5697137c52
The "permit/lejet" feature is really a subscription. Full rename of the mutable master data, plus a recurring monthly price. - DB (migration 0004, data-preserving ALTER RENAME): permits→subscriptions, permit_credentials/_plates→subscription_*, sessions.permit_id→subscription_id. - Pricing: per-subscription priceMinor + period(monthly) + currency, with a site default (site_config.subscription_monthly_price_minor) pre-filling the form. - Server: subscription-flow.ts (SubscriptionFlow), routes/subscriptions.ts (/api/subscriptions). Web: SubscriptionManager, route, i18n (sq Abonimet/en). - The signed ledger `permitId` payload is intentionally kept — immutable hash-chained history; renaming it would break verification of past events. Deferred (wiki notes): fee collection into the ledger/shift (a shift-attributed payment), LPR/ANPR plate source, time-of-day access windows (overnight subscriber). Also carries the device-footer UI surface (api DeviceStatus, router mount, i18n devices) due to shared-file overlap with the preceding footer commit. Verified end-to-end on a fresh DB and migration on a live-DB copy (sessions preserved). Live DB migrated. Full monorepo builds clean. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
58 lines
2.4 KiB
Markdown
58 lines
2.4 KiB
Markdown
---
|
|
type: concept
|
|
tags: [parking, domain, business, anti-fraud, access-control]
|
|
sources: []
|
|
updated: 2026-06-15
|
|
status: open
|
|
---
|
|
|
|
# Anti-Passback
|
|
|
|
Stop one credential/ticket from getting **two cars in** without an exit between — the classic
|
|
"pass the card/ticket back over the fence" abuse. A control on the entry validation, leaning on the
|
|
session projection.
|
|
|
|
## The rule
|
|
|
|
An identity (ticket id, [[subscription]] credential, or plate) **must not enter while it already has an
|
|
OPEN [[parking-session|session]].** At entry:
|
|
|
|
```
|
|
identify vehicle → is there already an OPEN session for this id?
|
|
no → proceed (mint vehicle_entry, open)
|
|
yes → passback violation → refuse or flag (see policy)
|
|
```
|
|
|
|
This is a **fold over the signed [[append-only-event-chain]]** ("does an entry for this id exist
|
|
with no matching exit?") — not a mutable in/out flag that could be edited. Same projection that
|
|
powers [[capacity-occupancy]] and [[subscription]] `maxConcurrent`.
|
|
|
|
## Interaction with the limits already designed
|
|
|
|
- **Transient ticket** — a single ticket id is inherently one session; a second entry on the same
|
|
id is always a violation (or a re-print/duplication attempt).
|
|
- **Permit** — passback is the *per-car* case of the permit's `maxConcurrent` ([[subscription]]): a
|
|
multi-car permit legitimately has several open sessions, but **the same car/credential** entering
|
|
twice is still a violation. So enforce per-identity, *under* the permit's concurrency allowance.
|
|
|
|
## Policy (operator choice)
|
|
|
|
- **Hard** — refuse the second entry (strict; risks stranding a legitimate car after a *missed
|
|
exit*, which is common — tailgated out, sensor missed).
|
|
- **Soft** — allow but **flag an `anomaly`** (the type exists) for review. Safer against
|
|
false-positives from missed exits, consistent with the append-only "record + flag, don't block"
|
|
ethos elsewhere.
|
|
- Likely **soft by default**, hard as an opt-in for high-control sites.
|
|
|
|
## Honest limits
|
|
|
|
- Depends on **reliable exit detection** — if exits are routinely missed (no exit loop/plate read),
|
|
passback produces false positives; tune to the site's exit fidelity.
|
|
- A spoofed/duplicated ticket QR is caught here (same id already open) — complements
|
|
[[ticket-encoding]]'s opaque-id requirement.
|
|
|
|
## Open
|
|
|
|
- Default policy (soft/hard) and per-site override.
|
|
- Grace for legitimate quick re-entry vs. the missed-exit false-positive.
|