--- type: concept tags: [parking, anpr, exit, threat-model, reconciliation, fraud] sources: [] updated: 2026-07-01 status: settled --- # Plate reconciliation at exit (ticket-swap defense) Uses the ANPR **plate as an invariant** to catch a **ticket-swap fraud**: the car's plate is the same regardless of which ticket it holds, so if a car tries to exit on a ticket whose plate is **already inside under a different ticket**, something is wrong. Built 2026-07-01 alongside [[operator-issued-entry]] (the capability that makes the fraud easy). The [[threat-model|adversary is the operator]], but the same swap happens innocently (two people mix up tickets). ## The fraud (worked scenario) A lot with 1000 spots: 1. Real car enters on ticket **1234** → ANPR records plate **AA123BB** at entry. 2. Car comes to exit owing 10,000 ALL. Operator scans 1234, **pockets the cash, does NOT record the payment**. 3. Operator **mints a fresh ticket 1237** (age ≈ 0 → owes ~0) and lets the car out on 1237. 4. **1234 lingers "inside" forever** — a phantom car. Repeat → +100, +200 phantom cars; occupancy becomes meaningless and the operator skims cash while the books look internally consistent (a ticket was "paid" — 1237 for 0; a ticket is "inside" — 1234). The plate is what the swap can't hide: entry-1234 = AA123BB, and the car exiting on 1237 **is** AA123BB. ## The check `ExitFlow.#reconcilePlateAtExit(exitingId)`: 1. Resolve the **exiting** ticket's plate (its own exit read, else its entry read). 2. Enumerate all **currently-open** sessions (projection cache) and their **entry** plates (`platesForIdentities`). 3. If the exiting plate **exactly** matches an open session under a **DIFFERENT** identity → **swap suspected**, returning `{ plate, otherIdentity, otherEnteredAt }`. **EXACT, HIGH-CONFIDENCE only.** Both the exiting read AND the matched session's entry read must be ≥ `PLATE_MATCH_MIN_CONFIDENCE` (0.85), normalized exact string match. No fuzzy/edit-distance matching. Rationale: ANPR is **advisory and misses** (G3H snapshot 503s, camera-side push failures, no-plate reads — see the ANPR memory notes). A fuzzy/low-confidence read must **never** be the reason a car is held — so a shaky read simply doesn't trigger the warning (fails toward not-annoying). ## What happens on a suspected swap ### Booth path (operator-mediated) — FLAG LOUDLY + require an override Exit fails-OPEN for safety and a plate is **never the sole gate**, so we do **not** silently hard-block (that would trap a legit car on a bad read). Instead: - `exitForBooth` returns status **`swap_suspected`** with the detail; the barrier does **not** open. - A **`anomaly` (`exit.plateSwapSuspected`)** is signed immediately — so even if the operator walks away, the suspicion is in the tamper-evident record. - The pay/exit modal shows a **prominent red warning** ("Plate AA123BB is already inside under ticket 1234, entered 3h ago") with an explicit **"Override & release"** action. - On override, `exitForBooth(id, { override, operator })` proceeds AND signs an attributed **`anomaly` (`exit.plateSwapOverride`)** — the override is itself a signed, named decision. ### Reader path (automated, no operator) — LOG-ONLY, fail-open At an unmanned exit lane there's no one to make the override decision, and exit fails-open, so the reader path **signs the `exit.plateSwapSuspected` anomaly and still lets the car out**. The anomaly is the control there (a manager reconciles it later). This is a smaller surface — the fraud scenario is booth-mediated. ## Why this is the right shape - **Occupancy stops drifting.** A swap can no longer silently strand ticket 1234 "inside" — the exit attempt on 1237 surfaces it. Directly serves [[capacity-occupancy]] integrity. - **The signed anomaly is the audit signal** a manager reconciles ([[reconciliation]]) — consistent with "the fraud control lives in the signed chain + human review, not a real-time hard gate". - **Advisory-not-a-gate is preserved both ways:** a plate never *opens* a barrier by itself, and now a plate never *traps* a car by itself either (flag + override, never a silent hard block). ## Relates - [[operator-issued-entry]] — the capability whose fraud this defends. - [[capacity-occupancy]] — occupancy integrity the swap attacks. - [[reconciliation]] — where the signed anomalies are ultimately settled. - [[entry-exit-points]] — the ANPR-on-snapshot path that records the plates compared here. - [[threat-model]] — operator-as-adversary.