Deferred/tracked (planned, not scheduled). When a connected camera has a known caveat, surface a dismissable notice with the fix instead of the caveat being silent. Sparked by the Uniview LPR (IPC6322SR-X22P-D): its Android SD-fallback is invisible unless the operator opens that camera's editor and scrolls to the compat card.
Scope decision: build the NARROW version (DB-matched quirk notices + runtime conditions Crumb already computes). Do not build a general "diagnostics engine" — most stream-health detection already exists as system-alert keys (camera_stream_rejected, stream_no_segments, storage_unwritable, camera_offline, decode-status, fs/check). The gap is presentation of per-camera standing conditions, not new detection.
Most of the detection is already shipped:
services/api/src/camera_compat.rs already matches make/model → entry with quirks[]/recommended_settings[] (two-tier Identified vs Possible).
go2rtc.rs::stream_served_video_lacks_fmtp already runtime-detects the served-SDP fmtp condition per camera every reconcile pass (for any camera, DB or not); today only used to gate rtsp_mainv_url.
Phase 1 — per-camera main-repair setting (prerequisite, independently useful)
The "Enable fix" action needs a per-camera setting; MAIN_REPAIR_TRANSCODE_ENABLED is a global env flag today (config.rs), and a transcode should be enabled on the one camera that needs it, not fleet-wide.
- Migration
cameras.main_repair_transcode boolean (nullable; NULL → fall back to the global env flag, the migration-0045 NULL→env precedence pattern). Register in MIGRATIONS (services/common/src/db.rs).
go2rtc.rs reconcile + dto.rs/playback.rs gating read the per-camera value; admin camera editor gets a toggle with a CPU-cost hint.
- Update
docs/AI-INSTALL.md env reference + docs-site configuration/environment-reference.md (golden rule 5) and the compat entry fix text.
Phase 2 — notices surface
- Add stable
id to each quirk in data/camera-compatibility.json (schema note in data/README.md) so dismissals survive rewording.
GET /notices (admin-only): aggregates per camera (a) Identified compat quirks and (b) runtime conditions already computed in-process — start with exactly the served-SDP fmtp verdict (covers unknown cameras too). Each notice {camera_id, notice_id, severity, summary, detail, fix_text, action?} where action is a declared server-side capability (enable_main_repair) the console renders as a button; no arbitrary actions. Recommend the free camera-side H264 switch first; the button is the fallback for cameras (like the LPR) that require H265.
notice_dismissals (camera_id, notice_id, dismissed_by, dismissed_at) — server-wide (admin-only surface), with a "show dismissed" toggle. Re-fire only on a changed quirk id.
- Admin console: badge on camera-list rows + dismissable banner on camera detail/dashboard, using the existing
esc()/api()/semantic-color conventions in admin.html.
Explicitly out of scope for v1
Push/webhook channels (caveats are standing facts, not events — do not page people about documentation). Per-user dismissal (one-operator model). The broad diagnostics engine.
Notes
- Warrants a DECISIONS.md entry (console-only dismissable notices; diagnostics-engine rejected; one-click actions are declared server-side capabilities only; per-camera
main_repair_transcode with NULL→env precedence replaces global-only gating).
- Desktop embeds
/admin, so it inherits Phase 2 for free; Android/iOS deferred.
Full plan (this session): the two-feature planning doc; this is the recommended first of the two.
Deferred/tracked (planned, not scheduled). When a connected camera has a known caveat, surface a dismissable notice with the fix instead of the caveat being silent. Sparked by the Uniview LPR (
IPC6322SR-X22P-D): its Android SD-fallback is invisible unless the operator opens that camera's editor and scrolls to the compat card.Scope decision: build the NARROW version (DB-matched quirk notices + runtime conditions Crumb already computes). Do not build a general "diagnostics engine" — most stream-health detection already exists as system-alert keys (
camera_stream_rejected,stream_no_segments,storage_unwritable,camera_offline, decode-status,fs/check). The gap is presentation of per-camera standing conditions, not new detection.Most of the detection is already shipped:
services/api/src/camera_compat.rsalready matches make/model → entry withquirks[]/recommended_settings[](two-tierIdentifiedvsPossible).go2rtc.rs::stream_served_video_lacks_fmtpalready runtime-detects the served-SDP fmtp condition per camera every reconcile pass (for any camera, DB or not); today only used to gatertsp_mainv_url.Phase 1 — per-camera main-repair setting (prerequisite, independently useful)
The "Enable fix" action needs a per-camera setting;
MAIN_REPAIR_TRANSCODE_ENABLEDis a global env flag today (config.rs), and a transcode should be enabled on the one camera that needs it, not fleet-wide.cameras.main_repair_transcode boolean(nullable; NULL → fall back to the global env flag, the migration-0045 NULL→env precedence pattern). Register inMIGRATIONS(services/common/src/db.rs).go2rtc.rsreconcile +dto.rs/playback.rsgating read the per-camera value; admin camera editor gets a toggle with a CPU-cost hint.docs/AI-INSTALL.mdenv reference + docs-siteconfiguration/environment-reference.md(golden rule 5) and the compat entryfixtext.Phase 2 — notices surface
idto each quirk indata/camera-compatibility.json(schema note indata/README.md) so dismissals survive rewording.GET /notices(admin-only): aggregates per camera (a)Identifiedcompat quirks and (b) runtime conditions already computed in-process — start with exactly the served-SDP fmtp verdict (covers unknown cameras too). Each notice{camera_id, notice_id, severity, summary, detail, fix_text, action?}whereactionis a declared server-side capability (enable_main_repair) the console renders as a button; no arbitrary actions. Recommend the free camera-side H264 switch first; the button is the fallback for cameras (like the LPR) that require H265.notice_dismissals (camera_id, notice_id, dismissed_by, dismissed_at)— server-wide (admin-only surface), with a "show dismissed" toggle. Re-fire only on a changed quirk id.esc()/api()/semantic-color conventions inadmin.html.Explicitly out of scope for v1
Push/webhook channels (caveats are standing facts, not events — do not page people about documentation). Per-user dismissal (one-operator model). The broad diagnostics engine.
Notes
main_repair_transcodewith NULL→env precedence replaces global-only gating)./admin, so it inherits Phase 2 for free; Android/iOS deferred.Full plan (this session): the two-feature planning doc; this is the recommended first of the two.