Add a third data stream (app_logs), distinct from the signed ledger and device telemetry, for operational/diagnostic logs — an offline appliance has no Sentry to ship to, so the host is the log store. Backend: a pino stream tees warn/error/fatal into app_logs (info/debug stay stdout-only) with no call-site change; the DB is built before Fastify so the logger has its sink. Frontend (lib/logger.ts): ships failed API requests (minus 401 churn), window.onerror, unhandledrejection, and a top-level React ErrorBoundary; console warn/error forwarded only at debug/trace. Batched/throttled POST, sendBeacon on pagehide, loop-safe (never logs the /api/logs call), best-effort everywhere. POST /api/logs (any signed-in user, CSRF, tolerant) + GET /api/logs gated by a new log:read permission (new `log` RBAC resource; admin holds it). Retention: pruned by age + row cap, hourly + at startup. UI: a Logs screen under /setup (filter level/source/since, expand to context+stack), sq+en. Migration 0009_app_logs. Verified end-to-end via app.inject: login -> POST 204 -> GET 200 with the record; backend warn/error persisted, info dropped; non-admin GET 403 / POST 204. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2.3 KiB
type, tags, sources, updated, status
| type | tags | sources | updated | status | ||||
|---|---|---|---|---|---|---|---|---|
| concept |
|
2026-06-15 | open |
Device Events (telemetry)
The unsigned operational record of what the hardware did and reported — distinct from the signed business append-only-event-chain (see event-streams-split). For monitoring, diagnostics, and live booth status — not anti-fraud.
What lands here
- Relays/barriers: relay fired/released, pulseOpen issued (the device-side echo; the
authoritative
barrier_open_commandis a signed ledger event). - Printers: paper-out / near-end / cover-open / cutter / offline (already polled — printer-status-monitoring).
- Cameras: reachable/offline, snapshot success/failure (lpr-camera).
- Readers / inputs: a raw read, raw input edges (Dingtian button
input N on/off— device-input-flow).
Properties
- Unsigned, not chained — no
prevHash/signature. It's telemetry, so it carries none of the ledger's integrity machinery. - Disposable — high-volume and churny; may rotate/prune on a retention policy (the ledger never does).
- Device-keyed — references the
devicesinstance (raw device provenance). Nolane(pool-of-spaces model — see entry-exit-points).
Not to be confused with app-logs.
device_eventsis hardware telemetry (a relay fired, a camera failed). Diagnostic/application logs (a failed API request, an uncaught frontend error, a backend warning) are a separate third stream inapp_logs— don't route app errors here, nor hardware telemetry there. Both are unsigned + prunable; the distinction is what produced it.
The boundary that matters
A device event is evidence the host saw something happen; it does not by itself authorize or
record a business fact. A button press here becomes a signed vehicle_entry in the ledger only
after the entry flow runs (ticket + barrier command). This keeps device chatter on the device side
of the device-adapter-pattern and the signed ledger focused on money/access.
Open
- Retention/rotation policy (size- or age-based).
- Whether any witness-grade device fact (e.g. a loop-sensor
barrier_open_observed) should also write a signed ledger entry for reconciliation — see append-only-event-chain.