Files
julian ee28b7302f docs(wiki): decide vision service packaging — apps/vision/ in the monorepo
Settle WHERE the host-side ANPR service lives and how it joins the build: in this
monorepo at apps/vision/ (not a separate repo), still a separate OS process called
over localhost HTTP, wired into the Turbo graph via a thin package.json shim whose
scripts shell to Python tooling (uv/uvicorn/ruff/pytest). Co-located source honors the
vision-service runtime+license isolation decision (AGPL reach is a linking boundary,
not a folder); the fast-alpr MIT baseline removes most of the split-repo pressure
anyway. New page vision-service-packaging; updates vision-service, opencv-anpr-service,
the CLAUDE.md layout, index, log. Not built yet — packaging decision only.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-19 14:25:31 +02:00

4.6 KiB

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)
│   └── vision/       # Python/FastAPI ANPR service (planned; separate process, Turbo shim — see wiki/decisions/vision-service-packaging.md)
├── 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.