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).
This commit is contained in:
@@ -24,7 +24,12 @@ status: open
|
||||
manager visit) to reconcile the signed log against an external authority — the real anti-fraud
|
||||
control. See [[reconciliation]].
|
||||
5. **Durability / backup.** Backup strategy for the [[sqlite]] database + recovery plan; "sync
|
||||
later" currently leaves a disk failure as **total revenue-history loss**.
|
||||
later" currently leaves a disk failure as **total revenue-history loss**. _(Confirmed in-scope
|
||||
to design, 2026-06-15.)_ Because the DB is the signed [[append-only-event-chain]], a backup must
|
||||
preserve the chain intact (a restored copy must still `verifyChain`); options include SQLite
|
||||
WAL/online-backup snapshots to a second disk/USB + the periodic external export that doubles as
|
||||
the [[reconciliation]] channel (#4). Encryption at rest already applies ([[disk-os-hardening]]).
|
||||
Design TBD.
|
||||
6. **Secure-element integration.** Confirm [[atecc608]] wiring/usage on the host (event
|
||||
signing). The [[esp32-custom-controller]] command-authentication use is **deferred — not
|
||||
being implemented for now** (access control is the [[dingtian-relay]] behind
|
||||
@@ -39,3 +44,15 @@ status: open
|
||||
compromising a verifying host yields nothing that can forge a token. Decide before
|
||||
multi-host / multi-lane deployment (see #1 lane topology), since that's when shared-secret
|
||||
distribution becomes the liability.
|
||||
8. **Exchange-rate (FX) system.** _(Raised by the [[tariff]] design, 2026-06-15.)_ Currency is
|
||||
selectable per tariff version and the money model is FX-ready (`payment` stores currency + a
|
||||
reserved `fxRate`), but **no conversion is built**. If multi-currency pricing/charging is ever
|
||||
needed, it requires an **offline** rate source (rates can't depend on the network —
|
||||
[[offline-first]]), a base currency, and a rounding policy. Deferred; nothing blocks adding it
|
||||
later without migrating stored amounts.
|
||||
9. **Pay-station money corners — receipts & refunds/change.** _(Raised by the scope sweep,
|
||||
2026-06-15; deferred until pay-station hardware is chosen.)_ Not yet designed: **receipts / VAT
|
||||
invoices** (fiscal receipt with tax number + sequential numbering may be legally required — could
|
||||
change what the `payment` event must store) and **refunds / overpayment / change** (cash change,
|
||||
"exact change only", a refund as a signed reversal event). Both depend on the unmanned-vs-manned
|
||||
payment subsystem (#3) and the note/coin/card acceptor hardware. Revisit at procurement.
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
type: decision
|
||||
tags: [parking, decisions, domain, business]
|
||||
sources: []
|
||||
updated: 2026-06-15
|
||||
status: open
|
||||
---
|
||||
|
||||
# Decision: Parking Session Model
|
||||
|
||||
The starting decision for the **business layer**, taken 2026-06-15 as the project pivots from the
|
||||
(now hardware-verified) device/integrity layer to the parking *operation*.
|
||||
|
||||
## Decisions
|
||||
|
||||
1. **A session is a projection over the signed event log, not a mutable table.** The
|
||||
[[append-only-event-chain]] `events` table stays the only source of truth; a
|
||||
[[parking-session]] is folded from `vehicle_entry` / `vehicle_exit` / `payment` / `void`
|
||||
events. A cache table is allowed for query speed but is always rebuildable and never
|
||||
authoritative.
|
||||
2. **Transient-first, mixed site.** Model the casual pay-for-duration session + [[tariff]] first;
|
||||
layer [[permit]] holders on top as a second identity source that short-circuits payment
|
||||
([[entry-exit-readers]]).
|
||||
3. **Pay-on-foot / pay station.** Payment is **decoupled from exit**: the customer pays at a
|
||||
central station; the exit lane only validates the session is paid and within the walk-back
|
||||
grace window before opening ([[parking-session]] lifecycle). Matches the
|
||||
[[autonomous-direction|unmanned]] roadmap and sharpens [[open-questions]] #3 toward an unmanned
|
||||
pay station (PCI scope still kept out of the app via a certified terminal).
|
||||
4. **New signed event types:** `vehicle_entry`, `vehicle_exit`, `payment`, `void` — extend the
|
||||
existing `input_received`. Recorded in [[append-only-event-chain]].
|
||||
|
||||
## Why (rejected alternative)
|
||||
|
||||
A **mutable `sessions` table** carrying `amountOwed` / `paidStatus` as the source of truth was
|
||||
rejected: it reopens the exact fraud vector the system exists to close ([[threat-model]] — the
|
||||
insider edits the row, marks it paid, pockets the cash). Making "paid" a **signed `payment`
|
||||
event** means it can't be forged and can't be silently deleted (a deletion breaks the chain). The
|
||||
projection approach costs a fold/cache but keeps the anti-fraud guarantee intact end-to-end.
|
||||
|
||||
## What this unblocks
|
||||
|
||||
Closes the dangling thread from [[device-input-flow]] ("the entry flow itself is the next
|
||||
build"): `input_received` → signed `vehicle_entry` → ticket print → `pulseOpen`, then the
|
||||
pay-station and exit-validation flows. Schema (`packages/db`) + shared types follow the
|
||||
[[parking-session]] + [[tariff]] design pages.
|
||||
|
||||
## Open / next
|
||||
|
||||
- Rate card, currency, grace windows, caps — operator/procurement input ([[tariff]]).
|
||||
- Tariff versioning (effective-dated) for historical repricing.
|
||||
- [[permit]] data model + lapsed-mid-stay handling.
|
||||
- Wire payment capture to a concrete pay-station terminal ([[open-questions]] #3) — kept abstract
|
||||
(payment = an independent signed event referencing a session) until procurement settles.
|
||||
- Reconciliation of sessions/payments against an external authority remains [[open-questions]] #4
|
||||
+ the unbuilt witness/reconciliation gap in [[append-only-event-chain]].
|
||||
@@ -14,6 +14,10 @@ The decisions treated as settled in the design notes. (See [[parking-system-arch
|
||||
- **Stack:** [[turborepo]] · [[fastify]] (Node) · [[react-vite-spa]] · [[sqlite]] +
|
||||
[[drizzle-orm]] · [[local-jwt-auth]]. All MIT/Apache/BSD — **no vendor lock, no rug-pull
|
||||
risk** (see [[payload-cms]]). Full table in [[technology-stack]].
|
||||
- **Scoped exception (2026-06-15):** the [[opencv-anpr-service]] — a **separate local process**,
|
||||
not linked into the app — **may use AGPL** components (plate/vehicle models). The exception is
|
||||
bounded to that process; the Node/React app stays strictly MIT/Apache/BSD. See
|
||||
[[vision-service]].
|
||||
- **Platform:** a **dedicated, hardened Linux appliance** (LUKS + GRUB password + Secure Boot),
|
||||
**not Windows/WSL** — see [[disk-os-hardening]].
|
||||
- **Integrity:** append-only, hash-chained, [[atecc608]]-signed event log
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
type: decision
|
||||
tags: [parking, decisions, vision, anpr, anti-fraud]
|
||||
sources: []
|
||||
updated: 2026-06-15
|
||||
status: open
|
||||
---
|
||||
|
||||
# Decision: Host-side Vision Service (ANPR + vehicle verification)
|
||||
|
||||
Taken 2026-06-15, as part of the business-layer build ([[session-model]]).
|
||||
|
||||
## Decisions
|
||||
|
||||
1. **Build a host-side vision service** ([[opencv-anpr-service]]) that does ANPR (plate → identity)
|
||||
**and** vehicle-attribute verification (anti-spoofing witness) on snapshots from ordinary
|
||||
Hikvision/Dahua cameras.
|
||||
2. **It replaces the dedicated edge-AI [[lpr-camera]]** as the recognition path: ordinary IP cam →
|
||||
snapshot (`Snapshot.bytes`, already pulled by the camera driver) → vision service → plate +
|
||||
vehicle. Removes the special LPR camera from the [[bom]] as a requirement (still allowed as an
|
||||
option).
|
||||
3. **Deployment: a separate local Python/OpenCV microservice** on the appliance, called over
|
||||
**localhost HTTP** by the Node backend. Fully offline ([[offline-first]]); its own process and
|
||||
failure domain; the host falls back to the ticket path if it's unavailable.
|
||||
4. **Licensing exception:** AGPL components (e.g. YOLO plate/vehicle models, OpenALPR) are
|
||||
**permitted inside this service only**, because it's a separate process not linked into the app —
|
||||
the app stays strictly MIT/Apache/BSD. Amends [[standing-decisions]].
|
||||
5. **Recognition is advisory, evidence is authoritative.** A read never single-handedly authorizes
|
||||
a paid/access barrier open; it flags for [[reconciliation]] and attaches (with the source image)
|
||||
to the signed [[append-only-event-chain]] entry. Low confidence → fallback, never strand a car
|
||||
([[fail-state-safety]]).
|
||||
|
||||
## Why
|
||||
|
||||
- **Replace vs. edge-AI camera:** host-side recognition on cheap IP cams shifts cost from per-lane
|
||||
smart cameras to one compute box + our software; gives us the raw image for the second job below.
|
||||
- **Vehicle verification is the real prize (user-driven, 2026-06-15):** plate-only ANPR can't catch
|
||||
a **printed/spoofed plate on a different car**. Extracting vehicle attributes/fingerprint lets the
|
||||
system reconcile *the car*, not just the number — directly filling the independent-witness gap the
|
||||
[[append-only-event-chain]] calls out as unbuilt.
|
||||
- **Separate-process + AGPL-scoped** keeps the app's permissive-license guarantee intact while not
|
||||
crippling accuracy (the strict permissive-only ANPR path is markedly weaker — that tradeoff was
|
||||
weighed and the scoped exception chosen).
|
||||
|
||||
## Rejected / alternatives
|
||||
|
||||
- **Strict permissive-only ANPR in-app** — license-clean but weaker accuracy and more build; the
|
||||
separate-process AGPL exception was chosen instead.
|
||||
- **Keep the edge-AI LPR camera as primary** — viable fallback if host-side accuracy disappoints;
|
||||
not chosen now, kept on the table in [[opencv-anpr-service]].
|
||||
- **Embed OpenCV in Node** (opencv4nodejs/WASM) — rejected: native-build pain, weaker model
|
||||
ecosystem, no process isolation, shares the app's failure + license surface.
|
||||
|
||||
## Open / next
|
||||
|
||||
- Recognizer + vehicle-model selection and accuracy targets; fingerprint method + anomaly
|
||||
threshold ([[opencv-anpr-service]]).
|
||||
- Appliance compute footprint (CPU vs. small GPU/NPU) — [[bom]] / [[open-questions]].
|
||||
- Service API + the Node-side adapter; per-camera opt-in wiring.
|
||||
- Reconciliation logic that consumes plate+vehicle witness vs. commanded opens (still unbuilt — see
|
||||
[[append-only-event-chain]], [[reconciliation]]).
|
||||
Reference in New Issue
Block a user