55d6242c7d
Permissions matrix rethink (wiki/decisions/venue-modules.md §"Permissions matrix", open-questions #16) — the grid stays the enforcement layer: - Move 1: each desk's money is guarded by that desk's own permissions. Manifest tillGuards {read, shift, cash}: booth = shift:read / shift:create / drawer:create (unchanged), carwash = carwash:read / carwash:cash (new). Shift + drawer routes resolve the guard FROM THE TILL (requireTill); a wash role holds no shift:* and cannot touch the booth by construction. Replaces the session:read borrowing (tillPermission). /api/shift/tills lists the role's readable tills with canWork; history/movements without a till filter return the union of readable tills. - Move 2: jobs — manifest permission bundles (booth-operator, booth-supervisor, merchant, wash-operator) as one-click chips in Setup → Roles, with "mixes desks" and "partial job" lints (warnings, never blocks). - Move 3: the live WebSocket admits any watch permission (event/session/device read or a module's feedPermission) and filters every push per role; report:read is the reports screen only. Auth: the token's roleId is only a hint — refreshRole() after every jwtVerify resolves the user's CURRENT role (cached, bumped on role/user writes), so reassigning a user's role applies on the next request and a deleted user's session ends with 401. Tests: till guards + look-only role, feed rules, every job's permissions exist, role reassignment without re-login. 353/353. Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
149 lines
12 KiB
Markdown
149 lines
12 KiB
Markdown
---
|
||
type: decision
|
||
tags: [parking, decisions, open]
|
||
sources: [parking-system-architecture]
|
||
updated: 2026-09-04
|
||
status: open
|
||
---
|
||
|
||
# Open Questions / Next Steps
|
||
|
||
**Not yet decided** (or decided-but-not-yet-built), and they drive everything else — settle before
|
||
procurement. (See [[parking-system-architecture]] §10.)
|
||
|
||
1. **Lane topology.** One host per lane, or one central host driving networked devices in each
|
||
lane? Decides how many controllers, printers, UPSs, and [[sqlite]] instances exist, and the
|
||
failure blast radius. (A single central host is a single point of failure for *all* lanes.)
|
||
2. **Failure modes.** Define per direction what happens to barriers on host/power/network loss —
|
||
particularly **fail-open on exit** for egress safety. Currently unaddressed. See
|
||
[[fail-state-safety]].
|
||
3. **Payment subsystem.** Manned booth (P2PE terminal + cash drawer) vs unmanned pay station;
|
||
confirm **PCI scope is kept out of the application** via a standalone certified terminal
|
||
(see [[bom]]). **No POS on any site yet (2026-07-01)** → card tender is **disabled in the UI**
|
||
(cash-only); the future-POS requirements + re-enable path are in [[card-payments]].
|
||
4. **Reconciliation channel.** Even if "offline," establish *some* periodic path (USB, hotspot,
|
||
manager visit) to reconcile the signed log against an external authority — the real anti-fraud
|
||
control. See [[reconciliation]].
|
||
5. **Durability / backup.** _(Design SETTLED + engine/target/UI BUILT 2026-06-29 — see
|
||
[[backup-recovery]]; engine + admin-configured target (site_config, migration 0016) + daily timer +
|
||
manual route + admin UI (target field, Test-target probe, status, Back-up-now) done; SFTP +
|
||
restore-tooling pending. The target dir is admin-chosen in the UI, not env; only BACKUP_KEY is a
|
||
server secret.)_ A disk failure / stolen-or-destroyed PC currently leaves **total revenue-history
|
||
loss**.
|
||
Settled design: an **admin-driven encrypted full-DB backup** (online-backup/`VACUUM INTO`, snapshots
|
||
included) to a **local/USB · SMB/NFS · SFTP** target, **manual button + in-process daily timer**,
|
||
**keep-last-N + dailies** retention, encrypted with a **dedicated `park_buzi_backup_key`** (separate
|
||
Komodo secret, *not* the signing key). Recovery = backup file **+** the two escrowed keys held
|
||
out-of-band; a restored copy must still `verifyChain`. **Key-custody stance:** `EVENT_SIGNING_KEY`
|
||
stays **decoupled from the TPM** (an extractable, escrowed software key) precisely so it survives
|
||
total hardware loss — the conscious trade against #6 (a TPM-sealed signing key would be unforgeable
|
||
but **unverifiable after the machine dies**). **Restore is admin-only/out-of-band** (operator-adversary
|
||
surface — [[threat-model]]). See [[backup-recovery]], [[fleet-deployment-komodo]], [[disk-os-hardening]],
|
||
[[reconciliation]] (#4).
|
||
6. **Secure-element integration.** _(Updated 2026-07-02: no secure element is on-site today.)_
|
||
Event signing currently runs on the **software `SoftwareSigner`** (HMAC-SHA256, key in
|
||
`EVENT_SIGNING_KEY` — an env var **on the host disk**). So the ledger is tamper-EVIDENT but
|
||
**not** unforgeable by anyone who owns the host: a case-opening adversary who decrypts the disk
|
||
reads the key and can re-sign a doctored chain (see [[append-only-event-chain]] "pull-the-disk",
|
||
[[disk-os-hardening]] physical-tamper chain). The **[[atecc608]] is UPCOMING, not present** — and
|
||
it isn't even the right host part: on a PC appliance the realistic non-extractable host signer is
|
||
the **[[tpm|TPM 2.0]]** the box already has, or a **USB HSM** (Nitrokey HSM 2 / SmartCard-HSM);
|
||
reserve the ATECC608 for the (deferred) [[esp32-custom-controller]]. The concrete menu +
|
||
recommendation (TPM interim → USB-HSM target) is in [[hardware-signer-options]]. The
|
||
controller command-authentication use is **deferred — not being implemented for now** (access
|
||
control is the [[dingtian-relay]] behind [[network-isolation]]); revisit only if prevention-grade
|
||
device auth becomes a requirement.
|
||
7. **JWT signing: symmetric vs. asymmetric key.** _(Raised by the commit security review, not the
|
||
source doc.)_ Auth currently uses a symmetric HMAC secret (`@fastify/jwt`, see
|
||
[[local-jwt-auth]]) — the same secret signs *and* verifies, so it must live on every host that
|
||
validates tokens. Consider rotating to an **asymmetric key (RS256 / EdDSA)** so the server
|
||
holds only the **public key** to verify; the private signing key can then live in the
|
||
[[atecc608]] or a key-management step. This mirrors the "store only the public key" property
|
||
already used for [[atecc608]] event signing and the [[challenge-response-auth]] scheme —
|
||
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.
|
||
10. **Snapshot retention.** _(Raised by the [[entry-exit-points]] camera-snapshot build, 2026-06-16.)_
|
||
Entry/exit snapshots are stored as BLOBs in the [[sqlite]] `snapshots` table. This grows the
|
||
single DB file fast (~100–300 KB per image × every entry **and** exit), and SQLite doesn't
|
||
reclaim deleted-blob pages without `VACUUM`. **Undecided:** pruning policy (age-based vs.
|
||
total-size cap), VACUUM cadence, and how this interacts with the #5 backup strategy (blobs
|
||
bloat every backup). Until decided, snapshots accumulate unbounded. See [[entry-exit-points]].
|
||
_(Update 2026-06-29: pruning is now disk-pressure based — see [[entry-exit-points]]; and the
|
||
settled #5 backup includes snapshot BLOBs by default, with a noted future "exclude snapshots"
|
||
toggle since they dominate backup size — see [[backup-recovery]].)_
|
||
11. **Appliance OS image → WebKitGTK version (Tauri dependency).** _(Raised by
|
||
[[desktop-shell-tauri]], 2026-06-21; narrowed same day.)_ The chosen
|
||
[[desktop-shell-tauri|Tauri v2 desktop shell]] renders through the **host's WebKitGTK**, not a
|
||
bundled browser. The risk reduces to which OS the appliance runs:
|
||
- **Best case — Ubuntu 26.04 LTS desktop (intended):** ships a current, distro-maintained
|
||
WebKitGTK → this question is **effectively resolved**; just confirm the built SPA renders on
|
||
the actual image and pin it.
|
||
- **Worst case — Windows 11 + WSL + Docker:** this **conflicts with the standing platform
|
||
decision** (Linux appliance, *not* Windows/WSL — see [[standing-decisions]],
|
||
[[disk-os-hardening]]) and a GUI shell doesn't live inside headless WSL/Docker. Fallback is
|
||
**no native shell — a kiosk browser** at the local [[fastify]]-served SPA (Electron only if a
|
||
standalone Windows installer is mandated). See [[desktop-shell-tauri]] for the decision table.
|
||
Close this once the appliance OS image is fixed and the SPA is verified against its WebView.
|
||
(Ties to #1 lane topology / image standardization.)
|
||
12. **TPM 2.0 hardening — implementation (to build).** _(Recorded 2026-06-21; analysis in [[tpm]].)_
|
||
On the Ubuntu 26.04 LTS appliance, harden using the host **TPM**: (a) **sealed-LUKS auto-unlock**
|
||
(`systemd-cryptenroll --tpm2-device`) so the encrypted disk auto-unlocks only on an untampered
|
||
boot → unattended reboot after power loss; (b) optionally hold the **non-extractable host
|
||
event-signing key** in the TPM (a new `Signer` impl — no `EventLog` change; mirrors the
|
||
[[atecc608]] swap), defeating the offline pull-the-disk-and-re-sign attack. **Must include:**
|
||
require **TPM 2.0** (reject 1.2), **prefer fTPM** + a per-op **PIN/auth policy** (not PCR-only —
|
||
bus-sniff), a **LUKS recovery passphrase**, and a **re-seal-on-update runbook** (kernel/GRUB/BIOS
|
||
updates change the PCRs and lock the disk). TPM **complements, never replaces**, [[reconciliation]];
|
||
it does nothing against a rooted live host or the operator. Moot in the Windows + WSL fallback. See
|
||
[[tpm]], [[disk-os-hardening]]; relates to #6 (host secure-element by platform) and #13.
|
||
13. **Startup chain-integrity self-check (to build).** _(Raised by the pull-the-disk trace,
|
||
2026-06-21.)_ `verifyChain()` exists and pinpoints any tamper, but **nothing invokes it on
|
||
boot** — a tampered DB loads and serves normally (detectable but undetected). Wire a **startup
|
||
self-check** that runs `verifyChain()` and, on a break, **flags degraded state / writes a signed
|
||
`anomaly` + alarms** (surfaced to the booth footer / next reconciliation). Open: refuse-to-serve
|
||
vs. serve-degraded — lean **serve-degraded + loud alarm** (fail-open on exit still governs;
|
||
refusing to boot could strand a lane). Software-only, independent of the TPM/[[atecc608]] hardware.
|
||
See [[append-only-event-chain]].
|
||
14. **Printer USB transport — confirm the on-site printer + bake the provisioning.** _(Recorded
|
||
2026-06-24; the transport code is built — see [[printer-usb-transport]].)_ The ESC/POS drivers
|
||
now drive **TCP (port 9100) OR local USB (`/dev/usb/lp0`)** behind one render layer, selectable
|
||
per device. **Open:** is the actual booth printer USB or network? (The site's verified units are
|
||
*networked* — Cashino `10.0.10.9`, Rongta `10.0.10.10` — so USB may be unused here; the original
|
||
BOM listed "Epson TM / Citizen (USB **or** network)", so a future site may need it.) If USB is
|
||
used, the **appliance image** must (a) load/keep the **`usblp`** kernel module bound to the
|
||
printer (CUPS can claim the interface first), and (b) ship a **udev rule** giving the non-root
|
||
server process write access to `/dev/usb/lp*`. Both are [[appliance-provisioning]] steps, **not
|
||
app code**, and are **unverified on hardware**. Close this once the printer transport per site is
|
||
fixed and (if USB) the udev/usblp rule is in the image and a real USB print is verified. Relates
|
||
to #1 (lane topology / image standardization). See [[printer-usb-transport]], [[rongta-printer]].
|
||
|
||
15. **Venue modules — Car Wash / Bar as peers of Parking.** _(Raised by the user, 2026-09-04.)_
|
||
Optional per-site modules on a shared venue core, with Parking itself becoming a module.
|
||
Name stays `parking-system` (settled 2026-09-05); validation stays for the Bar, only the
|
||
Lavazh station retires when Car Wash (the pilot module) ships. **Registry + Car Wash v1 are
|
||
built (2026-09-05), and so are tills** — shifts/drawers per money-taking module (a bay
|
||
payment lands on the wash operator's own till, never the booth's). Open: vision category
|
||
flag, bay camera, the Bar's scope. Full design and the remaining questions on
|
||
[[venue-modules]].
|
||
|
||
16. **Permissions matrix after venue modules.** _(Raised by the user, 2026-09-05.)_ The flat
|
||
`resource:action` grid was composed for one desk; a second desk (Car Wash) exposed borrowed
|
||
meanings (`session:read` as "works the booth till", `report:read` as "may open the socket")
|
||
and a composer at the wrong altitude. Decision + three moves (per-desk till guards, jobs on
|
||
top of the grid, a permission-scoped live feed) on [[venue-modules]] §"Permissions matrix";
|
||
moves built 2026-09-05. Open: default supervisor bundle, re-applying jobs after a module
|
||
update, signing role edits.
|