Live ops wall + pluggable camera providers with licence gating (server-26#198) #208

Merged
logan merged 5 commits from feat/live-ops-wall into main 2026-10-04 18:28:50 -04:00
Owner

Live ops wall (Region -> Incident -> Camera drill-down, KPIs, transcript ticker, scrubber incident bands, auto-tour, #inc=&cam=&tour deep links) on top of a pluggable public-camera provider registry with licence gating. Refs server-26#198 (does not close it: the DAA grant is still an owner action).

Licence gating (c2-core app/internal/cameras/)

  • Every provider defaults OFF; a missing/unreadable config/camera_providers doc means off (fail closed).
  • Stubs and PROHIBITED sources can never be enabled. Gated statuses (agreement required / unverified / non-commercial / paid) need an explicit admin acknowledgement, recorded with who/when in the settings doc AND audit_log; the audit write is a precondition (no record, no enable). An acknowledgement is bound to the licence status it was given for.
  • 511NY is agreement_required (no DAA grant per #198) -> off by default. /traffic/511 events follow the same switch and return disabled while off. NYC DOT (#201) and Westchester DPW (#202) are non-enableable stubs. Camera detection / frame retention not built (#199, #200); CameraSignalSink is only a seam.
  • 511NY client: configurable NY511_API_BASE, tolerant XML inventory parser (DTD/entity refused), https-only imagery, credit + NYSDOT as-is disclaimer travel with the data, descriptions shown unaltered (DAA).

Frontend

  • Admin -> Camera providers tab (licence status first, blocking confirm dialog for gated sources, health/key status), deep-linkable as /admin#cameras.
  • CameraFeed: page-wide live-stream cap (2, +1 for drill), off-screen/hidden-tab tiles stop fetching, stale/frozen badges, stall watchdog, attribution on the frame. SourceCredit renders credit + update time + disclaimer wherever provider data shows.
  • QA fixes in the last commit: pins in localStorage and pinned/docked map players no longer render once their provider is switched off (they bypassed the gate and lost their credit); the "no camera provider enabled" link to /admin is admins-only (it bounced everyone else to /).

Verification

  • c2-core pytest: 550 passed, 0 failed (after rebase on origin/main).
  • Frontend tsc: NOT run - no Node.js on the authoring box and no lockfile (filed #207). next build in the main-branch Docker build is the first typecheck this gets; a reviewer with Node should run npm install && npx tsc --noEmit in drb-frontend before merge.

🤖 Generated with Claude Code

Live ops wall (Region -> Incident -> Camera drill-down, KPIs, transcript ticker, scrubber incident bands, auto-tour, `#inc=&cam=&tour` deep links) on top of a pluggable public-camera provider registry with licence gating. Refs server-26#198 (does not close it: the DAA grant is still an owner action). **Licence gating (c2-core `app/internal/cameras/`)** - Every provider defaults OFF; a missing/unreadable `config/camera_providers` doc means off (fail closed). - Stubs and PROHIBITED sources can never be enabled. Gated statuses (agreement required / unverified / non-commercial / paid) need an explicit admin acknowledgement, recorded with who/when in the settings doc AND `audit_log`; the audit write is a precondition (no record, no enable). An acknowledgement is bound to the licence status it was given for. - 511NY is `agreement_required` (no DAA grant per #198) -> off by default. `/traffic/511` events follow the same switch and return `disabled` while off. NYC DOT (#201) and Westchester DPW (#202) are non-enableable stubs. Camera detection / frame retention not built (#199, #200); `CameraSignalSink` is only a seam. - 511NY client: configurable `NY511_API_BASE`, tolerant XML inventory parser (DTD/entity refused), https-only imagery, credit + NYSDOT as-is disclaimer travel with the data, descriptions shown unaltered (DAA). **Frontend** - Admin -> Camera providers tab (licence status first, blocking confirm dialog for gated sources, health/key status), deep-linkable as `/admin#cameras`. - `CameraFeed`: page-wide live-stream cap (2, +1 for drill), off-screen/hidden-tab tiles stop fetching, stale/frozen badges, stall watchdog, attribution on the frame. `SourceCredit` renders credit + update time + disclaimer wherever provider data shows. - QA fixes in the last commit: pins in localStorage and pinned/docked map players no longer render once their provider is switched off (they bypassed the gate and lost their credit); the "no camera provider enabled" link to /admin is admins-only (it bounced everyone else to /). **Verification** - c2-core pytest: 550 passed, 0 failed (after rebase on origin/main). - Frontend `tsc`: **NOT run** - no Node.js on the authoring box and no lockfile (filed #207). `next build` in the main-branch Docker build is the first typecheck this gets; a reviewer with Node should run `npm install && npx tsc --noEmit` in `drb-frontend` before merge. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
logan added 3 commits 2026-10-04 17:59:35 -04:00
Backend (c2-core):
- app/internal/cameras/: provider interface, licence model, per-provider
  catalog cache; one adapter per source in design-canvas/camera-sources.md.
  Implemented: 511NY (legacy shape + tolerant XML inventory parser, 511x
  endpoints UNVERIFIED), Caltrans, DelDOT, Ontario 511, DriveBC, Calgary,
  TfL, NSW, Fintraffic. Stubs: NYC DOT, Westchester DPW, TRANSCOM, CTroads,
  511NJ, Austin, TxDOT, MD CHART, WSDOT, Tallinn, Tarktee, Warendorf,
  TrafficLand, Windy, EarthCam.
- Every provider defaults OFF (config/camera_providers). Gated licences
  (agreement required / unverified / non-commercial / paid) need an explicit
  acknowledgement stored with who/when and written to audit_log; the audit
  write is a precondition. Stubs and prohibited sources cannot be enabled.
  Ack is bound to the licence status it was given for.
- GET /cameras (signed-in), GET/PUT /admin/camera-providers (admin only).
- /traffic/511 follows the ny511 provider switch and returns the DAA credit
  + NYSDOT as-is disclaimer; 511NY event descriptions no longer truncated
  ("may not alter the substance of a report").

Frontend:
- LiveView becomes the ops wall: Region > Incident > Camera drill-down,
  worst-first rail (MapView), KPIs, transcript ticker, scrubber incident
  bands, auto-tour, deep links (#inc=&cam=&tour), Esc steps up.
- Incident panel: nearest cameras (path-weighted), fused situational context
  (ADS-B, AIS, 511NY events, NEXRAD tile sample), units, call spine.
- MapView keeps every layer; adds Camera Viewsheds (approx.) overlay and a
  nearest-camera layer for the selected incident.
- CameraFeed hygiene: hidden-tab/off-screen gating, 2 concurrent HLS cap,
  last good frame + stale badge, frozen-frame signature, HLS stall watchdog,
  provider attribution rendered on the imagery.
- Camera/event popups show source credit, update time and the disclaimer.
- Admin > Camera providers tab with licence status, health, gated confirm.
- Camera detection is not built (blocked on #199/#200); seam only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- LiveView: localStorage pins carry image URLs, so they rendered (without
  credit) after an admin disabled the provider. Pins now show only while
  their provider is serving (server-26#198 licence gating).
- MapView: pinned/docked map players drop out once their provider leaves
  the serving list; credits are kept from the last answer so docked players
  keep attribution while the overlay is hidden.
- WallPanels: the "no camera provider enabled" link to /admin was a dead CTA
  for non-admins (bounced to /). Link shown to admins only, deep-linked to
  /admin#cameras; the admin page now honours a tab hash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
logan added 1 commit 2026-10-04 18:07:10 -04:00
- Feed/catalog error text (shown to every signed-in user via GET /cameras and
  /traffic/511 status fields) carried httpx's full request URL, including
  ?key= for Ontario 511 and the 511NY DAA key. Query strings are now dropped.
- TfL and Calgary licences are per GEV only (TfL's terms page 403'd), the same
  footing as Ontario 511, so they are UNVERIFIED (acknowledgement required)
  instead of ungated attribution_required. Attribution still renders.
- The map camera layer clamps its bbox to +/-180/+/-90; zoomed-out bounds
  produced a 422 ("could not reach server").

Refs server-26#198

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Review pass, fixes pushed as 2f21728:

  • Security: feed/catalog error strings (returned to every signed-in user by GET /cameras and /traffic/511) embedded httpx's full request URL including ?key= (Ontario 511 key; the 511NY DAA key once configured). Query strings are now redacted (cameras/base.safe_error), with a test.
  • Licence gating: TfL (terms page 403'd) and Calgary (per GEV only) were ungated attribution_required; they are now unverified (acknowledgement required, same as Ontario 511). Attribution still renders.
  • Map layer: zoomed-out Leaflet bounds exceeded +/-180/90 and GET /cameras answered 422; the bbox is now clamped.

Verified: c2-core pytest 552 passed, 0 failed. Frontend tsc --noEmit exit 0 at HEAD (portable Node 20 in a scratch copy, npm install without lockfile).

Review pass, fixes pushed as 2f21728: - **Security:** feed/catalog `error` strings (returned to every signed-in user by `GET /cameras` and `/traffic/511`) embedded httpx's full request URL including `?key=` (Ontario 511 key; the 511NY DAA key once configured). Query strings are now redacted (`cameras/base.safe_error`), with a test. - **Licence gating:** TfL (terms page 403'd) and Calgary (per GEV only) were ungated `attribution_required`; they are now `unverified` (acknowledgement required, same as Ontario 511). Attribution still renders. - **Map layer:** zoomed-out Leaflet bounds exceeded +/-180/90 and `GET /cameras` answered 422; the bbox is now clamped. Verified: c2-core pytest 552 passed, 0 failed. Frontend `tsc --noEmit` exit 0 at HEAD (portable Node 20 in a scratch copy, `npm install` without lockfile).
logan added 1 commit 2026-10-04 18:28:39 -04:00
logan merged commit 30bca25ecc into main 2026-10-04 18:28:50 -04:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logan/server-26#208