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:
+114
@@ -355,3 +355,117 @@ guarantee. Recorded in [[dingtian-relay]] (new Hardening section).
|
||||
`localAddress` set. Root cause noted as Windows-side (stray 192.168.1.x); this is the
|
||||
self-contained Linux answer.
|
||||
- Updated [[wsl-dev-networking]].
|
||||
|
||||
## [2026-06-15] design | Business layer kickoff — parking session model
|
||||
- Pivoted from the (hardware-verified) device/integrity layer to the business domain. Wiki-first.
|
||||
- KEY DECISION: a [[parking-session]] is a PROJECTION over the signed [[append-only-event-chain]],
|
||||
never a mutable table — a mutable sessions row with paid/owed would reopen the operator-fraud
|
||||
hole the whole system closes. "Paid" = a signed `payment` event (unforgeable, undeletable).
|
||||
- Scope (user): mixed site, TRANSIENT-FIRST; [[permit]] holders layered as a 2nd identity source
|
||||
that short-circuits payment. Payment = PAY-ON-FOOT / pay station (decoupled from exit; exit lane
|
||||
only validates paid + within walk-back grace). Matches [[autonomous-direction]].
|
||||
- New signed event types designed (not yet built): `vehicle_entry`, `vehicle_exit`, `payment`,
|
||||
`void` — extend `input_received`. Lifecycle OPEN→PAID→CLOSED (+VOIDED); overstay top-up is the
|
||||
one genuinely stateful edge case.
|
||||
- New pages: [[parking-session]], [[tariff]] (pure/data-driven fee fn; gracePeriodExit is a real
|
||||
pay-on-foot revenue param), decision [[session-model]]. Updated [[append-only-event-chain]],
|
||||
[[index]]. Closes the dangling entry-flow thread from [[device-input-flow]].
|
||||
- [[permit]] drafted + RESOLVED from user input: credentials = RF tag/chip/card + QR (optical
|
||||
reader). Car limits = two numbers: `registeredCars[]` whitelist + admin-set `maxConcurrent` (in
|
||||
at once) — enforced as a fold over the permit's open sessions. Identity = card/QR OR matching
|
||||
plate (either opens; card-sharing not prevented by design, caught by reconciliation). Autonomy =
|
||||
host-in-the-loop for everything → Dingtian stays sufficient, no new controller; permit entry
|
||||
fails closed if host down. Remaining open: reader hardware models; lapsed/revoked policy.
|
||||
- NEXT: schema (`packages/db`: permits/tariffs + session projection) + the
|
||||
input_received→vehicle_entry flow (closes the [[device-input-flow]] thread).
|
||||
|
||||
## [2026-06-15] design | Host-side vision service (ANPR + vehicle verification)
|
||||
- User: optionally bind camera images to an OpenCV service we build. Resolved scope: ANPR (plate →
|
||||
`IdentitySource='lpr'`); a **separate local Python/OpenCV microservice** on the appliance (Node →
|
||||
localhost HTTP), offline; it **replaces the dedicated edge-AI [[lpr-camera]]** (recognition on
|
||||
ordinary Hikvision/Dahua snapshots — reuses `Snapshot.bytes`).
|
||||
- LICENSING: best ANPR/vehicle models are AGPL/commercial vs. the MIT/Apache/BSD standing rule.
|
||||
Decision: **scoped AGPL exception** — allowed INSIDE the vision service only (separate process,
|
||||
not linked); app stays permissive. Amended [[standing-decisions]].
|
||||
- USER ANTI-FRAUD INSIGHT: a fraudster can print a registered plate and enter with a different car.
|
||||
→ service also does **vehicle-attribute / fingerprint verification**, so the *car* reconciles, not
|
||||
just the plate. This fills the independent-witness gap [[append-only-event-chain]] calls out:
|
||||
plate-on-different-car = anomaly. Recognition is advisory (confidence + ticket fallback), evidence
|
||||
(read + image) attaches to the signed event.
|
||||
- New pages: [[opencv-anpr-service]], decision [[vision-service]]. Updated [[standing-decisions]],
|
||||
[[lpr-camera]] (host-side supersedes edge-AI), [[permit]] (plate-spoof defence),
|
||||
[[append-only-event-chain]] (vision as witness), [[index]].
|
||||
- Open: recognizer/vehicle-model choice + accuracy; fingerprint method + anomaly threshold; appliance
|
||||
compute (CPU vs GPU/NPU); per-camera opt-in; the still-unbuilt reconciliation logic.
|
||||
|
||||
## [2026-06-15] design | Transient pricing — composable, versioned tariff
|
||||
- User: pricing is unknown + constantly changing → must be **admin-composable at runtime**, currency
|
||||
selectable, FX later. Reframed [[tariff]] from "config we ship with numbers" to a first-class
|
||||
editable entity.
|
||||
- DECISIONS: (1) rate structure = **stepped duration blocks + rolling-24h daily cap** (flat rate is
|
||||
one block; expresses first-hour/taper/cap with no special cases); (2) overstay top-up =
|
||||
**reprice the difference** (recompute entry→now − alreadyPaid); (3) tariffs are **effective-dated
|
||||
immutable versions** — edits publish a new version, sessions reprice against the version in force,
|
||||
the `payment` event records `tariffVersionId` (reproducible + fixed in the signed chain); (4)
|
||||
**one active tariff per site**, but modelled with id/scope so multi-tariff needs no migration;
|
||||
(5) **currency selectable (ISO 4217)**, money = `{minorUnits, currency}`, payment reserves a null
|
||||
`fxRate` → FX-ready, **FX engine deferred** (needs offline rate source — new [[open-questions]] #8).
|
||||
- Ships with **no rate card**; owner must compose+publish one (blank = free or gated, operator
|
||||
policy — open). Numbers in the page are illustrative, not defaults.
|
||||
- Wrote the pure integer fee algorithm into [[tariff]] (data model: `tariffs` + immutable
|
||||
`tariff_versions`). Updated [[open-questions]] (#8 FX), [[index]].
|
||||
- NEXT: schema (`packages/db`) for tariffs/versions + permits + session projection, then the
|
||||
composer UI + the input_received→vehicle_entry flow.
|
||||
|
||||
## [2026-06-15] design | Shifts (manned-only) + Z-report; drop time-based token
|
||||
- Q: what happens at operator shift end? Resolved scope, deliberately small.
|
||||
- Shifts exist ONLY in manned mode — a human accountability boundary. The fully-automated/unmanned
|
||||
system has NO shifts; the pay-station cash-collection cycle + [[reconciliation]] replace it.
|
||||
- Shift is NOT time-based: relief arrives late / no-shows / one operator forced into a double.
|
||||
→ **drop the 8h token expiry**; login valid **until explicit logout** (updated [[local-jwt-auth]];
|
||||
code change pending). Start/End Shift are **explicit, independent of login** — one login spans many
|
||||
shifts; a double = End then Start again, no re-login.
|
||||
- End Shift = sum signed `payment` events in the shift by tender → append a signed `shift_z_report`
|
||||
(type already in packages/shared, chained to prior Z) → **PRINT cash total + POS total (if a POS
|
||||
is configured)**. That's the whole human-side ask. No blind count / variance gate / manager
|
||||
override. Fraud control stays in the signed chain + later [[reconciliation]] (catch a skim after
|
||||
the fact, not at close). Blind-count documented as an explicit optional add-on, not built.
|
||||
- New page [[shift]]; updated [[local-jwt-auth]], [[index]].
|
||||
- Open: Z sums by payment-time (the operator who took the money) — confirm; X-report (read-only
|
||||
mid-shift); per-operator vs per-booth vs per-site (ties to [[open-questions]] #1). `payment` event
|
||||
needs a `tender` field (cash/card) — fold into the schema step.
|
||||
|
||||
## [2026-06-15] design | Scope sweep — capacity, validation, reporting, integrity gaps
|
||||
- "What else can a PMS do?" — swept the full feature surface against the design; user picked the
|
||||
in-scope gaps. New pages:
|
||||
- [[capacity-occupancy]] — occupancy = fold over open sessions; refuse entry + drive a FULL sign
|
||||
when full; **exit never blocked** ([[fail-state-safety]]); zone-ready; counting-drift = anomaly.
|
||||
- [[validation-discounts]] — merchant validates a ticket → **signed discount event** applied at
|
||||
fee time ([[tariff]]); over-validation visible to [[reconciliation]]; payment records gross/disc/net.
|
||||
- [[reporting-analytics]] — revenue/occupancy/stay/permit/anomaly reports as projections over the
|
||||
chain; **plate-search** (admin looks up a session by plate IF captured — honest "not captured").
|
||||
- [[clock-integrity]] — fees depend on the host clock; offline box → backdating attack; monotonic
|
||||
index catches reorder, clock-regression = `anomaly`, RTC + privileged-only time change.
|
||||
- [[blocklist]] — barred plates/cards refused at **entry only**; signed + attributed.
|
||||
- Folded into existing pages: **manual overrides** = signed reason-coded events (legitimate
|
||||
counterpart to the out-of-band-open anomaly) + **lost-ticket admin-arbitrary amount** →
|
||||
[[parking-session]] + [[tariff]]; **backup/restore** confirmed in-scope, expanded [[open-questions]]
|
||||
#5 (restored copy must still verifyChain; doubles as the reconciliation export).
|
||||
- NOT captured (flagged): **intercom/help-call** — user didn't select it, but it's the only human
|
||||
fallback for an unmanned lane; revisit. Deferred roadmap: reservations, mobile app, EV, loyalty.
|
||||
- Updated [[index]].
|
||||
|
||||
## [2026-06-15] design | Second sweep — ticket encoding + anti-passback; money corners deferred
|
||||
- More gap-hunting. New pages:
|
||||
- [[ticket-encoding]] — the transient session key: opaque/unguessable **ticket id printed as QR**
|
||||
by [[rongta-printer]], **scanned at pay station + exit** (new ReaderDevice/imager behind the
|
||||
adapter); plate-as-ticket ticketless alt coexists per lane. The physical backbone of the
|
||||
transient flow (was only implied).
|
||||
- [[anti-passback]] — one id can't enter while it already has an OPEN session (card/ticket-passing
|
||||
over the fence); a fold over the chain, *under* permit `maxConcurrent`. Soft (flag `anomaly`) by
|
||||
default vs. hard (refuse); honest dependence on reliable exit detection.
|
||||
- DEFERRED (user): **receipts/VAT invoices** + **refunds/change/overpay** — depend on pay-station
|
||||
hardware + manned/unmanned payment subsystem; recorded as [[open-questions]] #9, revisit at
|
||||
procurement (may change what the `payment` event stores → flagged before schema).
|
||||
- Still open & load-bearing: **lane topology** (#1) — not resolved; scopes sessions/occupancy/shifts.
|
||||
- Updated [[open-questions]] (#9), [[index]].
|
||||
|
||||
Reference in New Issue
Block a user