bfe64032d8
Turborepo (pnpm workspaces) with all dependencies pinned to latest mutually-compatible versions: turbo 2.9, TypeScript 6, Fastify 5, React 19, Vite 8, better-sqlite3 12 + Drizzle ORM 0.45. Layout: - apps/server Fastify backend (local JWT auth + role guard, /health) - apps/web React 19 + Vite 8 operator SPA - packages/db Drizzle schema on SQLite/WAL; append-only events + users - packages/devices reader/printer/relay adapter interfaces (intent-only relay) - packages/shared shared domain types Architecture constraints from the design wiki are encoded in the scaffold: append-only hash-chained + signed event log, device-agnostic adapters, "a barrier is not a door" (relay expresses intent only), fully-local offline-first auth. wiki/ is an LLM-maintained Obsidian knowledge base (28 pages) ingested from the architecture & design notes, with its own maintenance schema. Verified: pnpm install, full turbo build (5/5), server boots and serves /health, drizzle-kit generates the initial migration.
89 lines
4.5 KiB
Markdown
89 lines
4.5 KiB
Markdown
# Parking System — Project Guide
|
|
|
|
A parking-management system: a **web app on a dedicated, hardened Linux appliance**, deployed
|
|
on-site at a parking facility. Two forces shape almost every decision: **offline-first**
|
|
operation and a **threat model whose primary adversary is the legitimate operator at the booth**
|
|
(not an outsider). Keep both front of mind.
|
|
|
|
## Repository layout
|
|
|
|
This directory is a **Turborepo** monorepo. App code lives here; the knowledge base lives in
|
|
`wiki/`.
|
|
|
|
```
|
|
parking-system/
|
|
├── CLAUDE.md # this file — app development guide
|
|
├── package.json # turborepo root
|
|
├── turbo.json
|
|
├── apps/
|
|
│ ├── server/ # Fastify backend (device drivers, API, auth); serves the SPA
|
|
│ └── web/ # React + Vite SPA (operator UI)
|
|
├── packages/
|
|
│ ├── db/ # Drizzle ORM schema + migrations (SQLite local; PostgreSQL sync target)
|
|
│ ├── devices/ # device adapters behind shared interfaces (reader/printer/relay)
|
|
│ └── shared/ # shared types/utils
|
|
└── wiki/ # LLM-maintained knowledge base (Obsidian vault) — see wiki/CLAUDE.md
|
|
```
|
|
|
|
> Code layout above is the intended target; scaffold packages as the work reaches them rather
|
|
> than all up front.
|
|
|
|
## The wiki is the knowledge base — consult it first
|
|
|
|
`wiki/` is an LLM-maintained design knowledge base (the "LLM Wiki" pattern). It is **not app
|
|
code** and has its own schema at `wiki/CLAUDE.md`. Before making architectural decisions or
|
|
implementing a subsystem, **read the relevant wiki pages** for the rationale, rejected
|
|
alternatives, and open questions:
|
|
|
|
- Start at `wiki/overview.md`; catalog in `wiki/index.md`.
|
|
- Settled decisions: `wiki/decisions/standing-decisions.md`.
|
|
- Unsettled, procurement-driving items: `wiki/decisions/open-questions.md` — **do not hard-code
|
|
around these without flagging them.**
|
|
|
|
When app work surfaces a new design fact, decision, or contradiction, **update the wiki**
|
|
following `wiki/CLAUDE.md` (ingest/query/lint workflows). Source documents go in `wiki/raw/`.
|
|
|
|
## Stack (settled)
|
|
|
|
All dependencies are **MIT / Apache / BSD** — a hard constraint to avoid vendor lock-in and
|
|
license rug-pulls. See `wiki/entities/technology-stack.md` for the full table and rationale.
|
|
|
|
| Layer | Choice |
|
|
| --- | --- |
|
|
| Monorepo | Turborepo |
|
|
| Backend | Node.js + Fastify |
|
|
| Frontend | React (SPA, Vite), served by Fastify |
|
|
| Local DB | SQLite (`better-sqlite3`) + Drizzle ORM (Drizzle Kit) |
|
|
| Remote sync target | PostgreSQL (deferred — not a runtime dependency) |
|
|
| Auth | Local JWT (`@fastify/jwt`) + bcrypt + role guard (admin/operator/cashier/readonly) |
|
|
|
|
## Architecture constraints that bind the code
|
|
|
|
These are not negotiable defaults — they come from the threat model and safety analysis:
|
|
|
|
- **Offline-first.** Nothing in core operation may depend on a network. Auth, DB, and device
|
|
decisions must work air-gapped. No external identity provider; no cloud runtime dependency.
|
|
- **Append-only, signed event log.** Entry/exit events are **never edited or deleted** — a
|
|
"void" is itself an appended event. Events are **hash-chained** (each stores the prior event's
|
|
hash) and **signed by an ATECC608 secure element**. This is the core anti-fraud mechanism;
|
|
don't add update/delete paths to event records.
|
|
- **Device-agnostic adapters.** Business logic talks **only to interfaces** (reader/printer/relay),
|
|
never to a device SDK. Hardware swaps = a new adapter in `packages/devices`, nothing else.
|
|
- **A barrier is not a door.** Never drive a barrier as a timed "open for N ms" auto-close.
|
|
Physical safety lives in the barrier operator's firmware; the app only ever expresses **intent
|
|
("open")**. Relay interfaces are `pulseOpen`, never timed close.
|
|
- **Fail-state.** On power/network/host loss: **entry fails closed, exit fails open** (never trap
|
|
a vehicle — often a legal egress requirement).
|
|
- **Network isolation for access controllers.** The UHPPOTE controller speaks unauthenticated UDP;
|
|
it must sit on an isolated VLAN reachable only by the host. Treat its event log as
|
|
tamper-evident (host-side index tracking), not tamper-proof.
|
|
- **Keep PCI scope out of the app.** Payments go through a standalone bank-certified P2PE
|
|
terminal — the application must not handle card data.
|
|
|
|
For the full reasoning behind each, follow the links from `wiki/overview.md`.
|
|
|
|
## Conventions
|
|
|
|
- TypeScript throughout. Match the style of surrounding code.
|
|
- Confirm before destructive or outward-facing actions. Commit/push only when asked.
|