3 Commits

Author SHA1 Message Date
julian 59bfe2013f Event log: resolve input_received lane from the firing device
Replace the hardcoded lane: 0 on input_received events with a real
device->lane lookup. A new LaneMap caches lane_devices.id -> lane,
built at startup and refreshed by the setup routes on assign/unassign.
An unmapped device logs lane: -1 + a warning (0 is a real lane) and is
still recorded faithfully (append-only chain).

source stays null for raw inputs by design: it's an IdentitySource
(how a vehicle was identified), not a device field; device provenance
remains in identity. Documented both in the wiki.
2026-06-15 12:51:21 +02:00
julian 7db5cfa0e4 Dingtian: close password-less string-protocol relay-fire hole
The string protocol (UDP 60001) has no password field but can fire relays
("11" = relay 1 on), bypassing relay_pw entirely. Proven on hardware: an
unauthenticated packet opened a relay. harden() had left it enabled "for
status reads".

- #status() now reads via the authenticated binary command (relay cmd 0x00)
  instead of the string protocol, so the string protocol is no longer needed.
- harden() disables the string protocol (udp2.p=255). BEST-EFFORT: firmware
  V3.6J's config API silently refuses to disable udp2 (the device web UI can),
  so it's not part of the blocking verify -- harden() re-checks and returns a
  warning instead of throwing. After a web-UI disable, the attack is dead and
  binary control/status still work (verified on hardware).
- HardenResult gains an optional `warnings[]`; the assign route surfaces them
  to the admin and logs them.
- Corrected the false comment claiming relay_pw stops an attacker (it is
  defence-in-depth on plaintext UDP, not a boundary).
- Thread localAddress through the driver's UDP/HTTP calls so a multi-homed
  host sources device traffic from the device-facing NIC.
- Device web login (webUser/webPassword) is no longer redacted from setup
  state -- it's an operational credential for the admin-only device area;
  pushPassword/relayPassword stay machine-only.

Wiki: document the vuln + fix, the firmware caveat, and the out-of-band
actuation gap (the log captures host actions only; reconciliation vs. an
independent witness is the real control and is not yet built).
2026-06-15 11:29:55 +02:00
julian bfe64032d8 Initial scaffold: Turborepo monorepo + design wiki
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.
2026-06-14 00:34:11 +02:00