Camera Movement & Leasing
Two dispatchers are watching the same ridge camera during a wind event, and both want to point it at different smoke. A camera can only look one way. The lease is how the platform decides whose hands are on the wheel — and this API is the authority that decides it. (The frontend's lease deep-dive covers the client's view; this is the server that actually grants and expires the lease.)
The one idea to hold on to
The lease is granted as a side effect of a completed move, not requested — and the only hard
gate is the Camera.Operate role. A PTZ request doesn't create a lease. It queues the move to
the ImageAcquisition engine and returns; the engine executes it and then
calls back, and that callback records the lease:
// PanTiltZoomCamera → just queues the move to the engine
await _httpClient.PostAsync($"{_baseUrl}/QueuePanTiltZoomCamera", ...);
// later, the engine calls FunctionEndpoints/leaseCameraAfterMove →
var lease = await _cameraLeaseRepository.ExecuteLeaseProcess(leaseInsert);
So there's a real ordering: move accepted → engine moves → lease recorded. Meanwhile,
GET /lease/{cameraId} reports the current holder — which the frontend uses to warn a second
operator, not to block them. Play the arbiter's side of it:
No lease. The camera is free; anyone with the Camera.Operate role can move it.
Three things that sequence makes concrete: the lease is a side effect (the /ptz call
returns before it exists), Camera.Operate is the only hard gate (a viewer gets a 403,
everyone with the role gets through), and the lease is advisory — a second operator with the
role can move anyway, and the recorded holder simply becomes them.
Nothing on the server rejects a movement because someone else "holds" the camera. The
GET /lease result exists so the client can show a warning; the backend arbitrates by
recording whoever actually moved last. Two operators confirming at the same instant both
succeed. If you're expecting the API to enforce single-writer exclusivity, it doesn't — and by
design. The role check is the only thing that turns a request away.
Why grant-as-side-effect
Recording the lease only after the engine confirms the move keeps the record honest: a lease
means a move actually happened, not that someone asked. It also means the API stays a thin,
fast queue-and-return for the common case, and the durable, replay-safe part (the actual camera
control) lives in the engine where it belongs. The cost is the ordering subtlety above — the
/ptz response can't hand back a fully-formed lease, because it doesn't exist yet.
Wrapping up
The lease protocol's authority is this API, but the mechanism is indirect: /ptz queues the
move to the engine and returns, the engine calls back to record the lease via
ExecuteLeaseProcess, and GET /lease reports the holder for the client to warn on. The role
is the only hard gate; the lease itself is advisory. Movement, extend, close, and home round out
the lifecycle.
Where does the role — and the bearer token carrying it — come from? Next: Authorization.