0c218179c4
The SQLite DB is the signed append-only ledger, so a disk failure / stolen or destroyed PC means total revenue-history loss (open-question #5). This is the first slice of the backup-recovery design: the engine + a local/mounted target + a daily timer + a manual route. Engine (apps/server/src/backup.ts): - Consistent online copy of the live WAL DB via better-sqlite3's native .backup() (not a raw file copy, which can capture a torn WAL) — the restored copy is a byte-identical, queryable DB. - AES-256-GCM with a scrypt-derived key from BACKUP_KEY; self-describing header (magic|version|salt|iv|...|authTag) so a restore tool needs only the key + file. Zero new dependencies (Node crypto). - The plaintext intermediate is kept in scratch (not the removable/network target) and wiped in a finally, success or fail. - Retention: keep-last-N + one-per-day within N days. Wiring: - BackupService (env config, single in-flight guard, last-success/last-error). - routes/backup.ts: GET /api/backup/status (backup:read), POST /api/backup/run (backup:create), 409 when unconfigured. No restore route — restore is an out-of-band runbook action on a fresh appliance, not a console call. - New permission resource in @parking/shared. - server.ts: an unref'd daily timer, a no-op until BACKUP_TARGET_DIR + BACKUP_KEY are set, deliberately not run at startup (a just-power-cut booth shouldn't write to a possibly-unmounted disk). - openRawDb() added to @parking/db/testing (open a file without migrating, for restore-verification tests). BACKUP_KEY is deliberately SEPARATE from EVENT_SIGNING_KEY (independent rotation; backups travel, the signing key shouldn't). SMB/NFS work as mount paths; SFTP + admin UI + restore runbook are deferred slices. Tests: round-trip byte-identical, GCM tamper/wrong-key fail, short-key rejected, scratch cleaned, route auth/RBAC + 409. build/lint/test green (212 server tests). Wiki + open-question #5 updated. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
9.7 KiB
9.7 KiB
type, tags, sources, updated, status
| type | tags | sources | updated | status | ||||
|---|---|---|---|---|---|---|---|---|
| decision |
|
|
2026-06-29 | 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.)
- 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.)
- 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.
- 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).
- 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.
- Durability / backup. (Design SETTLED + first slice BUILT 2026-06-29 — see backup-recovery;
engine + local/mounted target + daily timer + manual route done, SFTP/UI/restore-tooling pending.)
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 dedicatedpark_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 stillverifyChain. Key-custody stance:EVENT_SIGNING_KEYstays 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). - 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 network-isolation); revisit only if prevention-grade device auth becomes a requirement.
- 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. - 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 (
paymentstores currency + a reservedfxRate), 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. - 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
paymentevent 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. - Snapshot retention. (Raised by the entry-exit-points camera-snapshot build, 2026-06-16.)
Entry/exit snapshots are stored as BLOBs in the sqlite
snapshotstable. This grows the single DB file fast (~100–300 KB per image × every entry and exit), and SQLite doesn't reclaim deleted-blob pages withoutVACUUM. 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.) - Appliance OS image → WebKitGTK version (Tauri dependency). (Raised by
desktop-shell-tauri, 2026-06-21; narrowed same day.) The chosen
desktop-shell-tauri 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.)
- 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 newSignerimpl — noEventLogchange; 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. - 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 runsverifyChain()and, on a break, flags degraded state / writes a signedanomaly+ 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. - 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 — Cashino10.0.10.9, Rongta10.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 theusblpkernel 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.