Repository navigation
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the normative observation/evidence/change-event/classification contract that every lightNVR (L*) and YA-WAMF (W*) task implements: field-by-field schemas, evidence states and bounded reasons, the io.lightnvr.detection.observation_changed.v1 event, cursor encoding, watermark and 410 rules including DB-restore epoch detection, the error catalog, capability document, annotation namespace and owner precedence, permission mapping, provisional budgets, reserved identifiers (0095/0096, authz bit 19) and a PRD traceability table. Point EVENT_CONTRACT.md at the reserved event type and link the PRD status line to the contract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add object_observations, observation_assets, observation_changes, observation_publications and observation_meta per the frozen OBSERVATIONS_CONTRACT: change rows snapshot evidence_state/reason (D6), publication state allows the five D7 values, and observation_meta holds the DB-generation epoch and prune boundary (D9). Add per-camera evidence columns on streams and the global history/queue settings, with a full migrate:down. Regenerate the embedded migration header. test_observation_migration covers the fresh schema, constraint rejections, recording delete detaching observations, and 0094 -> 0095 -> down -> up schema equivalence with a fresh database. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (C2)
Add getIntegrationCapabilities, listObservations, getObservation,
getObservationSnapshot and putObservationClassification to the canonical
OpenAPI document with the frozen authorization mapping (contract §12.1),
plus the Observation*, IntegrationCapabilities and ObservationError
schemas. The PUT key is two path segments, {namespace}/{name}; it is
authenticated-only until L13 adds observations.annotate to the catalog.
Register all five routes as authenticated 501 not_implemented stubs in
src/web/api_handlers_observations.c so the coverage gate holds, and add
scripts/contracts/coverage.mjs to refresh coverage.json (line numbers,
handlers, sha256, and new routes via --add-coverage).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (C4) Add tests/performance/observations: a repeatable workload harness (N looped RTSP streams via standalone go2rtc, continuous recording, local SOD detector at a fixed interval), pure metrics/gate helpers with unit tests, a compare CLI for C4 repeatability and the X2 release gate, and two feature-disabled baseline runs with host description and raw samples. fixture-server gains an optional extraConfig INI suffix so X2 can enable evidence budgets without editing the harness. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Canonical fixtures for the observations contract in tests/fixtures/integrations/observations/, generated from one coherent scenario by scripts/contracts/observation-fixtures.mjs: observations in every evidence state, two birds sharing an asset, a letterboxed HTTP transform, full MQTT envelopes for created/evidence/classification changes with UUIDv5 message IDs (contract 5.4 test vector included), chained history pages, error bodies, capability documents, classification PUT exchanges (201, no-op retry, 409 revision_conflict, 409 owner_decision_locked, 422 evidence_mismatch, 403 namespace_forbidden) and a 160x80 evidence JPEG with a box manifest. observation-fixtures.test.mjs validates every fixture against the C2 OpenAPI schemas (Ajv oracle plus generated validators), rejects malformed variants, recomputes message IDs, cursors, pages and box math, and checks the YA-WAMF vendored copy when present. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds db_observations: idempotent batch inserts, compare-and-set evidence transitions, the contract section 7.3 cursor page algorithm, publication staging for the L5 relay and history pruning. Every mutation is one BEGIN IMMEDIATE transaction under the DB mutex (db_auth.c pattern) that writes the observation/asset rows, change rows (with the evidence state at that revision) and pending publications with deterministic UUIDv5 message IDs together. Reads use a private read-only WAL snapshot. Also adds base64url encode/strict decode, a UUIDv5 helper (mbedTLS SHA-1) for L5 to reuse, and loads/saves the migration 0095 per-camera evidence columns on stream_config_t, normalizing unset budgets to the contract defaults so zeroed configs never violate the CHECKs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…T_01 L4) Startup reconciliation (contract section 14), run from main.c before the evidence worker and relay start: - D9 cursor-head sidecar check first, before any transition advances the head: same epoch with DB head < sidecar head regenerates the epoch; a missing or malformed sidecar is recreated. - Delete *.jpg.tmp and every <uuid>.jpg that no ready asset row points at (orphans, unfinished pending writes, stale expired/unavailable media). - pending -> unavailable(interrupted); ready with a missing file -> unavailable(write_failed), all through the L2 CAS transitions so every referencing observation gets an evidence revision and publication. Periodic bounded pass from the storage manager heartbeat (rate-limited to 5 min unless a bound was hit; no-op until startup reconciliation ran): - history prune past observation_history_retention_days (advances observation_meta.oldest_seq); - reclaim assets left without observations (expire, unlink, delete row); - per camera: expire unprotected ready media past the age budget, using the newest reference so shared assets survive until their last reference expires, then evict oldest unprotected media until within the byte budget. Pending assets are never touched. Expiry keeps metadata. - budget-exhausted flag when over budget with only protected media left; exposed via observation_retention_budget_exhausted() and pushed to L3 through a weak observation_evidence_set_budget_exhausted() reference. Only files resolved inside <storage>/observations are ever deleted, so recordings are never touched. db_core.c regenerates the observation cursor epoch after either restore_database_from_backup() call succeeds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1 L5) Register io.lightnvr.detection.observation_changed.v1 (info, operational, 1 h expiry, camera subject) with a section 6.3 payload validator, and add the relay that turns staged observation_publications rows into envelopes. - event_producer_enqueue_durable() exports the router -> outbox path with an enqueued/duplicate/no_route/mqtt_disabled/full/failed result. The existing static producers keep their 0/-1 behavior. - The relay wakes on a post-commit hook from the DB layer (or observation_relay_notify()) plus a 1 s poll, reads bounded seq-ordered batches, marks relayed / relayed_no_route only after the outbox step, then publishes on the in-process bus. FULL or an error leaves the row pending and backs off; expired rows become expired and invalid rows failed. It drains synchronously (bounded) at startup. - D8: pauses while 2,000 undelivered outbox rows of the type exist. - D9: writes <storage>/observations/.cursor_head.json (at most 1/s) and regenerates the epoch at startup when the sidecar shows a rewound head. - Stats getter for L10. Started after L4 reconciliation in main.c and stopped before the event bus on both shutdown paths. Tests: test_observation_relay (crash drain, duplicate, no route, route suppression, cap, error backoff, expiry, notify, sidecar, C3 fixture byte equality) and validator cases in test_event_envelope. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… L3) Producers hand an owned RGB24 frame (or the exact submitted JPEG bytes for the HTTP detector) plus the observation batch to observation_evidence_submit*(). Admission is lock-free with respect to the worker: atomic count/byte reservations (global and per camera), a Treiber stack for items, a preallocated Vyukov ring for drop records and sem_post wakeups. It never touches the DB or blocks on I/O. The single worker inserts the batch (asset pending), encodes with the shared jpeg_encoder_cache code, writes <asset>.jpg.tmp, fsyncs, renames, fsyncs the directory and only then commits pending -> ready. Failures unlink the temp file and record unavailable(encode_failed|write_failed). queue_full and budget_exhausted drops are inserted as unavailable(<reason>); dropped_untracked counts drops lost when the ring is full. The worker and ring start lazily on the first submission, so nothing is allocated while evidence is disabled. Shutdown drains for a bounded window and records the rest as unavailable(interrupted). main.c initializes it between L4 reconciliation and the L5 relay and stops it after the relay. Files live at <storage>/observations/<camera_uuid>/<yyyy-mm-dd UTC>/<asset_uuid>.jpg (path stored relative to the storage root). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the C2 501 stubs for the four GET operations (the P2 PUT stays 501
until L14):
- GET /api/integrations/capabilities: installation source from
event_identity, contract versions, per-principal operations
(history/snapshot authorized when live.view/snapshot.create is held on any
camera; annotate unsupported until L14), limits, history retention and
oldest cursor/observed_at, MQTT delivery topic, the D3 writer namespace
(normalized username, reserved names -> null) and up to 1,024 cameras with
the observation actions held.
- GET /api/observations: D1 comma lists (<=10 cameras, <=8 labels),
since/until RFC 3339, limit 1..500, cursor/head. The scope is the
intersection with cameras where the principal holds live.view; an explicit
unauthorized or unknown camera is 403. invalid_cursor 400, cursor_expired
410 with oldest_cursor/oldest_observed_at.
- GET /api/observations/{id}: live.view on the capture-time camera via
httpd_evaluate_camera_identity_action; unknown, deleted-camera and
unauthorized IDs are byte-identical 404 observation_not_found.
- GET /api/observations/{id}/snapshot: ready streams only this asset's file
(image/jpeg, Cache-Control private max-age=300, ETag "<asset_id>", 304 on
If-None-Match); pending 202 + Retry-After; unavailable 404 with reason;
expired 410 with reason/expired_at; D4 403 for live.view without
snapshot.create, 404 otherwise. Never substitutes another frame; a ready
row whose file is gone is 404 evidence_unavailable(write_failed), and a
stored path outside <storage>/observations is refused. Each fetch is
audited as snapshot.create on target_type observation (camera, asset,
state, outcome), honouring the allowed-read decision mode.
observation_record_to_json() follows the C3 fixture conventions (pending
nulls size/expiry; never-had-media nulls every media field; no path), and
is exported for L14 together with the time and namespace helpers.
OpenAPI: drop the 501 response from the four GET operations and regenerate
the projections; observations-contract.test.mjs now expects 501 only on PUT.
Tests: test_api_handlers_observations runs the handlers against a migrated
DB with wrapped authz/serve/audit: every status code, authorization
intersection, D4, path containment, and exact equality with the C3
observation and error fixtures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…T_01 L9) Expose observation_evidence_enabled, observation_labels, observation_max_age_days and observation_max_bytes on stream GET (list, detail, full) and accept them on POST/PUT (camera.configure). The handlers enforce the contract section 13 bounds that migration 0095 does not: a boolean flag, <= 16 trimmed labels of 1..31 printable ASCII bytes (empty means every label), 1..90 days and 64 MiB..1 TiB. Invalid values return 400 before any work is queued; changes never restart the stream. OpenAPI documents the fields (optional on read for additive compatibility) and the generated projections are refreshed. StreamConfigModal gains an "Observation evidence for integrations" section, off by default, noting a bird-capable object detector is required. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1 L6) Hand each accepted SOD/TFLite/multi-engine inference result to the L3 evidence queue while the decoded RGB24 frame is still alive: - detect_on_decoded_frame keeps the embedded-model inference buffer past zone/object/threshold filtering and submits it with the surviving, label-eligible detections (one batch_id per accepted result). - detect_with_runtime_engines records each engine's slice of the combined result and submits one batch per object engine (padded copy for all but the last); motion results never become observations, API engines stay out (L7). - The detection-stream thread captures on the inference thread, so the asset and transform use the detection stream's resolution. - New sidecar include/video/detection_evidence.h / src/video/ detection_evidence.c: batch building (engine provenance, model basename, clamped boxes, confidence clamp, track only for track_id >= 0 with namespace = engine key and session = per-process boot UUID), case-insensitive eligible-label matching, transform (SOD resize to the network size via new sod_model_input_size(), LiteRT letterbox mirroring letterbox_rgb24()), and the owned-buffer capture. - Evidence settings are cached in the UDT context at start and refreshed by unified_detection_set_observation_settings(), called from the PUT stream worker; no DB access on the detection/packet threads. detection_result_t, store_detections_in_db_for_camera and event_producer_publish_detection are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(INT_01 L15)
GET /api/detection/results/{name} now links each detection to the
observation captured by the same, already-authorized camera in the same
second +/-1 with the same label (ASCII case-insensitive, nearest second,
then earliest, then UUID), using idx_object_observations_camera_time on
one read-only connection. A linked detection gains an optional, closed
"observation" member {id, owner_confirmed, classification}, where
classification is owner_confirmed[0] before automated[0] or null. All
existing fields are unchanged; unlinked detections carry no member.
The timeline player and the recording video modal list each linked
observation with the detector label and confidence, the classified
label, confidence and classifier, an owner-confirmed badge, and a link
to /api/observations/{id}. The canvas overlay label appends the
classification to the detector result.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…T_01 L11) Playwright API contracts against the native binary: - observationsHistory: A7 camera-scoped isolation on list/get/snapshot for policies and scoped tokens; rename, delete and name reuse never transfer access; A8 distinct pending/expired/unavailable/unknown/expired-cursor outcomes; A5 cursor pagination with pending->ready recovery. - observationsReliability (MQTT): SIGKILL then restart drains committed changes in reconcile -> evidence -> relay order; relayed-but-unacked changes are redelivered under the same message IDs; DB restore turns old cursors into 410; broker down with 100 observations recoverable via the outbox and the cursor. - observationsPipeline (MQTT): a real HTTP detection stream produces two sibling observations sharing the exact inference JPEG, one message ID per revision. test_observation_crash_boundaries SIGKILLs a forked pipeline at six persistence boundaries and restarts it, asserting no ready asset points at an unfinished file and every committed change reaches the outbox exactly once. The kill points are stage hooks reachable only from linked code (no config, environment or API path). The fixture server gains kill/relaunch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bservation_pipeline (INT_01 L12) - New docs/integrations/YA_WAMF.md: bird-capable detector, per-camera evidence opt-in and budgets, MQTT prefix and event routes (no label predicate), scoped user/token with live.view, snapshot.create and observations.annotate, YA-WAMF settings, verification, P2 write-back, retention and cursor expiry, diagnostics, delivery guarantees, rollback and the supported-version matrix. - EVENT_CONTRACT, MQTT_INTEGRATION and DETECTION_ENGINES describe the observation event, its routing rule and which engines produce evidence. - PRD status and PRD index updated; contract section 18 points at the published matrix. - OpenAPI (L10 follow-up): SystemInformation gains an optional observation_pipeline (ObservationPipelineDiagnostics) mirroring observation_diagnostics_to_json(); projections regenerated. The web type's failures map is widened to match the generated type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cklist (INT_01 X1) tests/integration/yawamf_e2e runs docs/integrations/YA_WAMF.md step by step against real processes: Mosquitto 2.0.22, the lightNVR binary, and the YA-WAMF backend from feature/lightnvr-provider. A pinned launcher replaces only classify_async_live, the feeder is an ffmpeg HTTP MPEG-TS source, and a loopback HTTP detector finds the fixture birds by colour. It then checks A1, A2 (HTTP, plus an optional local SOD run on a real bird photo), A3, A4 (10x replay overlapping backfill), A5 (broker down for 100 observations, both apps restarted), A6 (SIGKILL of lightNVR and of YA-WAMF mid-burst) and A8 (temporary, expired, unavailable, and an expired cursor after a DB restore). Host-process mode was executed and every scenario passed (results/x1-host-20261108.json). The docker-compose container mode is committed as the documented CI path but was not run. Recipe corrections: - stream names cannot contain spaces; - PUT /api/streams answers 202 and applies asynchronously; - role and policy writes need expected_policy_version; - detection_threshold in the stream API is a percentage; - YA-WAMF needs a fixed build to start with LIGHTNVR__CAMERA_UUIDS set. The supported-version matrix now records what X1 ran and how. A real HTTP or LiteRT bird model, YA-WAMF's real classifier and A14 stay pending. The run found three YA-WAMF bugs, fixed on its branch (b89846d0, fcf449f2, 37c0877f + 1cbbae74). The pre-fix A6 failure is kept as results/x1-host-20261108-prefix-a6.json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tate) (INT_01 X3)
Extend the X1 host-process suite with p2_scenarios.py. YA-WAMF now starts
with LIGHTNVR__WRITEBACK_ENABLED=true (--no-writeback for the P1 recipe only).
- A12: the species appears on the YA-WAMF row, in observation_classifications,
GET /api/observations/{id} and the detection-results enrichment; the
classification change (revision 3) triggers no classifier call, intent
change or new write-back job; replays, identical retries and stale/future
revisions keep one annotation; an owner correction arrives as
owner_confirmed and a later automated result is refused on both sides.
- A13: a lightNVR outage during write-back leaves the YA-WAMF detection intact
and "sync pending"; the write-back completes after lightNVR returns.
- A7-annotate: no annotate action gives 403, another camera's scope 404, and
neither writes; the annotate token creates and retries cleanly.
Full run (X1 + X3, 454 s) passes against YA-WAMF 1cbbae74; the RED run with
write-back off is kept. Supported-version matrix and contract section 18
updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tency gate (INT_01 X2) X2's saturation run found that a dropped 720p evidence submit spent 1.5-2.6 ms on the detection thread inside free() of the 2.7 MB RGB frame (an munmap on a busy, many-threaded process), breaking the L3 "never blocks, < 1 ms" admission contract. Tracked drops now hand the frame to the worker through the drop record; pending deferred frames are bounded by max(global queue bytes, 32 MiB) and fall back to an inline free past that bound or when the drop ring is full. Observability for the A11 gate: - lightnvr_observation_submit_latency_seconds histogram (+ max gauge) for every admission that reaches the queue; - configured limits (items/bytes, global/camera/discard), the per-camera high-water mark and deferred/inline discard counters. Harness (tests/performance/observations): - run-workload.mjs: --system-setting K=V (restart once), L10 feature metrics scrape, post-shutdown DB summary (observations, assets by state:reason, detections); - metrics.mjs: histogram helpers, observationFeatureSummary, assetReasonSummary, evidenceGate (evidence happened, limits held, drops carry reasons, no failures, submit < 1 ms, optional saturation); - compare.mjs gate: same binary required, evidence checks for enabled runs; - make-bird-clip.sh: detection-producing clip recipe (photo not committed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lize C1 budgets (INT_01 X2) Seven 600 s runs of the C4 fixture on 9ed02bb with a bird clip (every inference detects): 3 disabled, 2 enabled at defaults, 2 enabled with a 4 MiB queue (56% queue_full). All four disabled/enabled gates pass: 0 recording gaps, segment-interval p95 -0.004%..+0.04%, evidence and DB observations present, limits held, every drop recorded with its reason (0 untracked), no failures, producer submit max <= 405 us (p99 <= 50 us). The DB-mutex probe fails its own 2% repeatability (2.4-5.0% between disabled runs) and is advisory under C4's rule; its pooled p95 rises +9.9% (defaults) / +6.5% (saturated) with an unchanged median, recorded as a finding (likely WAL autocheckpoints under the global DB mutex). compare.mjs: --advisory KEY and a pool command (pooledComparison). Contract §13 budgets are final with their X2 basis; §19 records the result; YA_WAMF.md, DETECTION_ENGINES.md and the scan-cap comment no longer call them provisional. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…nce UI text The detection-thread observation_evidence_enabled flag was an atomic_bool, which the C->Rust conversion turns into atomic_load/atomic_store on bool (rustc E0511). Use atomic_int 0/1 like the rest of the context struct. The per-camera evidence settings now describe the feature as working for any detector label instead of reading as bird-only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…CTOU) The startup evidence sweep checked each entry with lstat() and then opened, removed or rmdir'ed it by path, so an entry swapped for a symlink in between could redirect the action outside the evidence root (CodeQL cpp/toctou-race-condition). Walk with fdopendir/fstatat(AT_SYMLINK_NOFOLLOW) and act with openat(O_NOFOLLOW)/unlinkat on the directory fd, as hls_cache_cleanup.c does. The configured root itself is still followed. Tests pin the behaviour around it: nothing below the root is followed, and a symlinked root is still swept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…st conversion The C->Rust conversion turns atomic_bool loads, stores and exchanges into intrinsics that reject bool (rustc E0511). The existing atomic_bool sites are patched per file in tools/rust-transpile/repairs.py; the new INT_01 flags (evidence queue start/stop/worker state, per-camera budget_exhausted, and the retention startup latch) use atomic_int 0/1 instead, so they need no repair entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated by tools/rust-transpile/generate.py for 2d24bdd.
Address code-quality review on #933: the Mosquitto, lightNVR and YA-WAMF launchers opened their log files inline in Popen and never closed the parent's handle; open them in a with-block (the child keeps its own descriptor). Remove the unused `checks` and the overwritten `started` in run_e2e.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The External IP setting held one address and became one go2rtc candidate, and setting it dropped the "*" auto-detect candidate. Users reaching the NVR both over the LAN and over Tailscale/VPN could only get WebRTC (and two-way audio) on one of those paths; the other fell back to MSE. The setting is now a list (commas, semicolons or whitespace; up to 8 entries, 255 characters). Each entry becomes its own candidate: IPv4/hostname[:port], IPv6 or [IPv6][:port], "*"/"auto" for auto-detect, or stun:host[:port]. The settings API rejects an invalid list with 400 instead of writing a malformed or YAML-breaking candidate, and an invalid saved value falls back to auto-detect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…RTSP POST /api/onvif/device/test answered every failure with 500 "Failed to connect to ONVIF device", whether the camera rejected the credentials, was unreachable, or worked over ONVIF but refused the stream it advertised. - test_onvif_connection_detail() classifies the failure (credentials rejected via HTTP 401/403 or a NotAuthorized SOAP fault, ONVIF service unreachable, no usable profile, or stream not openable) and builds a message with credentials and query strings removed. The handler returns it as 502, since the camera rather than this server failed. - Some cameras advertise rtsps://host:322/... with secure RTSP disabled while plain RTSP on 554 works. When an advertised rtsps:// stream cannot be opened, the test now retries rtsp://host:554 with the same path. - test_onvif_connection() keeps its 0 / non-zero contract for the stream create/update callers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated by tools/rust-transpile/generate.py for 22e3fb8.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
lightNVR now keeps a durable record of each object detection, with a stored photo, and delivers it over MQTT at least once. That delivery survives broker outages and restarts. A client can also write its own classification back onto the detection.
This works for any detector label. Each camera's
observation_labelssetting picks the labels to keep (for exampleperson,car,package), and an empty list keeps every label. The API and events have no bird-specific fields. Any client with theobservations.annotatepermission can write classifications under its own namespace.YA-WAMF is the first consumer and the integration we tested end to end: it picks the bird observations, identifies the species, and writes it back as
yawamf/species. The PRD for that integration isdocs/prd/INT_01_Event_Enrichment_YAWAMF.md, and the contract isdocs/integrations/OBSERVATIONS_CONTRACT.md.The matching YA-WAMF change lives on
feature/lightnvr-providerin a separate repo and isn't part of this PR.What it adds
pending,ready,unavailableorexpired, and retention runs per camera.PUT /api/observations/{id}/classifications/{namespace}/{name}. The PUT needs the newobservations.annotatepermission. A cursor that has gone stale, or that came from before a database restore, gets410 cursor_expired.docs/integrations/YA_WAMF.mdhas the deployment recipe and the supported-version matrix.Testing
tests/integration/yawamf_e2e/runs real Mosquitto, lightNVR and YA-WAMF as host processes. All 10 scenarios pass (A1–A8, A12, A13, A7-annotate):tests/performance/observations/results/.9ed02bb4fixes that.Also in this PR
3ddcc03b).:port, IPv6,*orautofor auto-detect, orstun:host[:port].1c0d0712).POST /api/onvif/device/testused to return a generic 500 for every failure. It now returns a 502 that says what failed: the camera rejected the credentials (HTTP 401/403 or aNotAuthorizedSOAP fault), the ONVIF service couldn't be reached, the camera returned no usable profile, or the stream couldn't be opened. Credentials are removed from the message.rtsps://stream address, for example on port 322, while secure RTSP is turned off. When such an address can't be opened, the test now retries plainrtsp://on port 554 with the same path.atomic_int, so the C-to-Rust conversion builds.Known gaps and follow-ups
tests/integration/yawamf_e2e/A14_PILOT_CHECKLIST.md).observations.annotateby default.Reviewing
rust/isn't touched; CI regenerates it.web/generated/api/validators.js,include/web/openapi_document_generated.h) and committed performance results.int01-event-enrichment-yawamf-plan.mdholds the per-task logs.🤖 Generated with Claude Code