Connect a camera — ALPR auto-sessioning

Cameras turn a Turtini lot into an unattended gate: a plate read at entry opens a session, a plate read at exit closes it, and (with auto-pay on) the driver is charged off-session without ever scanning anything.

Three pieces have to be wired:

**1. Enable ingest on the lot**
`/parking → Lots → edit your lot → Camera + auto-pay` block. Toggle on:
• **Accept plate reads from registered cameras** — opens the ingest endpoint for this lot.
• **Auto-charge plates-on-file on exit** — runs the off-session Stripe charge when a session closes.
• **Show a QR code at the gate for cards not on file** — generates a Stripe Checkout link as a fallback for drivers who haven't registered a payment method yet.

**2. Register the camera**
`/parking → Cameras → + Register a camera`. Pick the lot, the position (Entry / Exit / Roving / Count-only), and the hardware kind:
• **Smart camera (on-device ALPR)** — Genetec, Vaxtor, OpenALPR appliance: read happens on the device, the device POSTs the result.
• **IP cam + cloud ALPR** — any RTSP stream piped to Plate Recognizer, Rekognition, or your own model; the inference layer POSTs the result.
• **ONVIF / PTZ** — generic ONVIF cameras driven by an integration partner.
• **Staff phone (PWA scanner)** — a phone with the camera permission as a roving patrol scanner.
• **Generic webhook** — anything that can POST a plate event.

When you save, the system mints a one-time API key (`pkc_...`). **Copy it now** — it's not shown again. You can rotate it later from the camera row.

**3. Point the camera at the ingest endpoint**
`POST https://us-central1-turtini.cloudfunctions.net/ingestParkingPlateEvent`
Headers: `x-camera-key: pkc_<your-key>`
Body (JSON):
```
{
"cameraId": "<from the camera row>",
"plate": "ABC1234",
"plateRaw": "ABC-1234",
"state": "MA",
"confidence": 0.94,
"direction": "entry",
"capturedAt": "2026-06-01T07:42:11Z",
"capturedPhotoUrl": "https://...optional snapshot..."
}
```

What happens next:
• A row appears in **Live plate events** within ~1s.
• If `direction=entry` (or `unknown` with no open session for that plate at that lot), a new `parkingSessions` doc opens — stamped with the resolved driver if the plate is on file.
• If `direction=exit` (or `unknown` with an active session), the matching session closes — the existing parking JE + new auto-pay pipeline fire.

**Confidence floor**: each camera carries a `confidenceMin` (default 0.8). Reads below the floor are dropped silently — set it lower if your gate environment is dim or your ALPR is conservative.

**No double-fire**: a repeat `entry` read while a session is already open is logged with reason `duplicate_entry_read` and otherwise ignored. A standalone `exit` read with no open session is logged with reason `no_active_session_on_exit` — typically the camera missed the entry.

Wally tool: `parking_listLotCameras` shows the cameras on a lot and their liveness (last plate seen, when). If a camera goes quiet, the dashboard surfaces it.