Files
parking_solution/wiki/entities/permit.md
T
julian 8a8e74561d wiki: design the business layer (session, tariff, permit, vision, shift, ops)
Pivot from the hardware/integrity layer to the parking operation. All
wiki-only; no code yet. Core principle throughout: business entities are
projections over the signed append-only event log, never mutable tables.

New concepts: parking-session, tariff (composable/versioned, FX-ready),
shift (manned-only Z-report), capacity-occupancy, validation-discounts,
reporting-analytics, clock-integrity, ticket-encoding, anti-passback.
New entities: permit, opencv-anpr-service, blocklist.
Decisions: session-model, vision-service (host-side ANPR + vehicle
verification; scoped AGPL exception for the isolated service).

Updates: append-only-event-chain (new event types + vision witness),
local-jwt-auth (drop 8h expiry -> until logout; code change pending),
lpr-camera (host-side recognition supersedes edge-AI), standing-decisions
(AGPL exception), open-questions (+FX, +pay-station money corners, backup).

Deferred + flagged: intercom/help-call, receipts/refunds/change, FX engine,
lane topology (#1).
2026-06-15 17:41:38 +02:00

6.3 KiB

type, tags, sources, updated, status
type tags sources updated status
entity
parking
domain
business
subscriptions
identity
2026-06-15 open

Permit (Subscription)

A subscription: a known holder authorized to enter/exit without paying per-stay, for a covered period. The second of the "two populations" (entry-exit-readers); a valid permit short-circuits the payment step of a parking-session (session-model). Transient is built first; permits layer on top.

Credentials (how a permit is presented) — confirmed with operator 2026-06-15

A permit is recognized by a credential read at the lane. Two kinds, mapping to the two identity paths:

  • RF tag / chip / card. An RFID/proximity credential. Read host-side (reader → host → pulseOpen): autonomy isn't required (resolved below), and the dingtian-relay has no onboard card list anyway, so there's no need to route RF into a controller. A Wiegand-out reader is still fine and keeps a future autonomous path open (entry-exit-readers), but isn't required.
  • QR code. Read by the optical reader — inherently host-side (entry-exit-readers: pure optical/network readers are invisible to a controller). Host decodes the QR → looks up the permit → decides.

Both feed the host as a reader event whose source is wiegand / qr (the IdentitySource already in the model) and whose value is the credential id.

Two optional, independent bindings — confirmed 2026-06-15

A permit has two constraints the admin may or may not apply, orthogonally. Either, both, or neither — the four combinations are all valid.

1. Car-count binding (default: 1)

  • Optional. By default a permit is bound to 1 car at a time. The admin may raise the limit (a household, a company fleet) or unbind it entirely (no cap on how many cars use it).
  • The limit is on cars inside at once (maxConcurrent), enforced over the parking-session projection: at entry, count the permit's currently-open sessions; if < maxConcurrent (or unbound) allow, else reject (allowance full). This is exactly why sessions-as-projection matters — "how many of this permit's cars are inside right now" is a fold over open entry/exit events, not a counter someone can edit.

2. Plate binding (default: off)

  • Optional. By default a permit is not plate-bound — any car may use it (identity is the card/QR). The admin may bind it to a set of specific licence plates.
  • When bound, an allowed plate is an accepted identity in its own right — a valid card/QR OR a matching plate opens the lane (either, not a second factor):
entry: read card/QR → find permit → car-count ok → open
   OR  LPR plate ∈ permit's bound plates → find permit → car-count ok → open
  • Accepted tradeoff: card-OR-plate is the most convenient but does not prevent card-sharing (a lent card still opens). Fine for a trusted permit population; the signed append-only-event-chain records exactly which credential/plate entered, so abuse is visible to reconciliation after the fact.
  • Plate-spoofing defence: a printed copy of a registered plate on a different car is caught not here but by the opencv-anpr-service's vehicle-attribute verification — the seen car must reconcile with the permit's known car, not just the plate string.

The two are independent: a plate-bound permit may have no car cap; a car-capped permit may accept any plate. The binding fields are simply absent/null when a constraint isn't applied.

Data model (first cut — to firm up with session-model)

A permits table (and supporting rows). Unlike the event log, reference/master data like permits is mutable (an admin grants/revokes/renews) — but every use of a permit still produces a signed vehicle_entry/vehicle_exit event in the append-only-event-chain, so the audit trail stays append-only even though the permit record itself is editable.

Field Notes
id, holderName/contact the subscriber
credentials[] one or more: { kind: 'rf' | 'qr', value }
maxConcurrent car-count binding; default 1, raise for fleets, or null = unbound
plates[] plate binding; default empty/false = any car; when set, these plates are accepted identities
validFrom, validTo coverage window
status active / suspended / revoked

Both bindings are nullable/empty by default — a bare permit is "1 car at a time, any plate, identified by its card/QR".

Interaction with the session model

  • Entry: credential read → permit lookup → valid (active, in window, plate allowed if plate-bound, concurrent cars < maxConcurrent if car-bound) → signed vehicle_entry (source = wiegand/qr/lpr), open barrier. No ticket, no fee. (A bare permit applies neither extra check — just active + in window.)
  • Exit: credential/plate read → matching open permit session → signed vehicle_exit, open. No payment required.
  • Lapsed mid-stay: permit expires while a car is parked → the uncovered time falls back to the transient tariff (edge case to design).
  • Revoked: a revoked permit fails the entry check → treated as transient (take a ticket) or refused, per policy (OPEN).

Resolved (2026-06-15)

  • Two optional bindings, independent: car-count (maxConcurrent, default 1, raisable or unbound) and plate-binding (plates[], default off = any car). Either, both, or neither.
  • Plate vs. credential: when plate-bound, card/QR OR matching plate — either is accepted identity (not a second factor); card-sharing not prevented by design, caught by reconciliation after.
  • Autonomy: host-in-the-loop for everything — no onboard card list needed, so the dingtian-relay stays sufficient (no new controller). Permit entry fails closed if the host is down (fail-state-safety). One code path for transient + permit.

Open questions

  1. Reader hardware — confirm the RF reader and the QR/optical reader models (procurement; relates to bom and open-questions). RF need not be Wiegand now that autonomy isn't required, but a Wiegand-out reader keeps options open.
  2. Lapsed-mid-stay & revoked policy (fall back to transient tariff vs. refuse) — confirm with operator.