Researched Node options for the UHPPOTE controller and chose the official
`uhppoted` npm package (MIT, actively maintained, single trivial dep). Its API
covers the full design: openDoor, getStatus, the event-log set (getEvent,
getEventIndex, setEventIndex, recordSpecialEvents), and setListener/listen.
Rejected alternatives: raw-dgram DIY, node-red-contrib-uhppoted, Go sidecar.
- packages/devices: add uhppoted@0.9.0; new `uhppote` access driver implementing
AccessControlDevice (pulseOpen -> openDoor intent-only; healthCheck/getDoorStatus
-> getStatus). Registered in the catalog alongside zkteco/esp32-relay.
- Local ambient types (uhppoted ships none); CJS interop via default-import +
destructure under NodeNext.
- Config fields: controller serial (required), optional host/protocol (udp|tcp),
doors, timeout.
Security/safety unchanged: unauthenticated UDP -> isolated VLAN assumed; relay
is intent-only ("a barrier is not a door").
wiki: record the library choice on uhppote-controller + log entry.
Verified: builds; `uhppote` shows in the catalog with correct fields;
instantiates and degrades to "offline" gracefully without hardware (on-VLAN
test pending).
- open-questions #7: symmetric vs. asymmetric JWT signing key (from the
commit security review). Prefer RS256/EdDSA so verifying hosts hold only a
public key — mirrors the ATECC608 / challenge-response "public key only"
property. Decide before multi-host/multi-lane deployment.
- Mark esp32-custom-controller status: deferred per decision not to build
device-level auth now; access control stays on UHPPOTE + network isolation
(noted in open-questions #6).
- local-jwt-auth: document hardened secret handling + 8h expiry and the
asymmetric-key pointer.
- Update index.md and append a log.md entry.
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.