wiki: split signed business ledger from device telemetry
Correction before schema work: the events table conflated the anti-fraud business ledger with device telemetry. Decision: ledger_events (signed, chained, reconciled) holds only business facts; device_events (unsigned, prunable) holds relay/printer/camera/reader/input telemetry. A raw button press is telemetry; the entry flow mints a signed vehicle_entry. Drops input_received-as-signed-event. New: decisions/event-streams-split, concepts/device-events; updated append-only-event-chain, index, log.
This commit is contained in:
@@ -21,9 +21,25 @@ Three layered properties:
|
||||
self-consistent — someone who owns the machine still cannot forge a valid entry.
|
||||
|
||||
It only becomes trustworthy as an external fraud control when paired with [[reconciliation]]
|
||||
against an authority the operator can't alter. Every device event — including those ingested
|
||||
from the [[uhppote-controller]] via [[event-log-ingestion]] — should land in this host-side
|
||||
chain.
|
||||
against an authority the operator can't alter.
|
||||
|
||||
## Two event streams — the signed ledger vs. device telemetry (decision 2026-06-15)
|
||||
|
||||
These are **different concerns and live in different tables**:
|
||||
|
||||
- **`ledger_events`** — this signed, hash-chained, [[atecc608]]-signed **business ledger**:
|
||||
`vehicle_entry` / `vehicle_exit` / `payment` / `void` / `shift_z_report`, plus the witness-grade
|
||||
`barrier_open_command` / `barrier_open_observed` and `anomaly`. This is the anti-fraud record that
|
||||
[[reconciliation]] runs against; sessions/[[tariff]]/occupancy are projections over it. (This is
|
||||
the table formerly called `events`.)
|
||||
- **`device_events`** — **unsigned operational telemetry**: relay fired, printer paper-out, camera
|
||||
offline, reader read, raw input edges. High-volume, churny, **not** anti-fraud; may rotate/prune.
|
||||
Keeping it out of the signed chain keeps the ledger small and high-value.
|
||||
|
||||
> A raw button press is **device telemetry**, not a business fact. It lands in `device_events`; the
|
||||
> entry flow then mints a **signed `vehicle_entry`** in the ledger once a ticket prints and the
|
||||
> barrier is commanded. (This supersedes the earlier "every device event lands in the chain" framing
|
||||
> and the `input_received`-as-signed-event approach — see [[device-input-flow]].)
|
||||
|
||||
## Implementation (apps/server)
|
||||
|
||||
@@ -58,14 +74,9 @@ so old events stay verifiable.
|
||||
> *accidental* corruption, but an operator with the signing key + DB access could re-sign a
|
||||
> forged chain. This is the central reason #6 matters.
|
||||
|
||||
### What currently feeds the log
|
||||
### Business-layer event types (the ledger)
|
||||
|
||||
Dingtian **input (button) pushes** → bus → `input_received` events (see [[device-input-flow]],
|
||||
[[dingtian-relay]]). These are recorded faithfully as raw inputs, **not** as `vehicle_entry` —
|
||||
the richer entry event waits for the entry flow (ticket print + barrier command).
|
||||
|
||||
**Business-layer event types (designed, not yet implemented — see [[session-model]]).** The
|
||||
[[parking-session]] domain folds over these signed events, extending `input_received`:
|
||||
The [[parking-session]] domain folds over these **signed ledger** events:
|
||||
|
||||
- `vehicle_entry` / `vehicle_exit` — a stay's endpoints; `identity` carries the ticket id or plate.
|
||||
- `payment` — a settled fee at the pay station, referencing the session it pays for (amount in
|
||||
@@ -73,20 +84,21 @@ the richer entry event waits for the entry flow (ticket print + barrier command)
|
||||
whole point: an operator can't forge it or silently delete it.
|
||||
- `void` — a correction / lost-ticket write-off; like every other void here it is an **appended
|
||||
event, never an erasure**.
|
||||
- `shift_z_report` — the signed per-[[shift]] takings summary.
|
||||
|
||||
A session is a **projection** over this chain, never a mutable table — the same anti-fraud reason
|
||||
the chain exists. See [[parking-session]].
|
||||
|
||||
- **`lane`** is now resolved from the firing device. A `LaneMap` (`apps/server/src/lane-map.ts`)
|
||||
caches `lane_devices.id → lane`, built at startup and refreshed by the setup routes on every
|
||||
assign/unassign. Device events carry the device instance id, not a lane; the handler looks it
|
||||
up. A device with no mapping (assigned without a lane, or a stale id) logs **`lane: -1`** and a
|
||||
warning — never `0`, which is a real lane — and is still recorded (the chain is append-only;
|
||||
nothing is dropped).
|
||||
- **`source` stays `null`** for `input_received`, and deliberately so: `source` is an
|
||||
`IdentitySource` (`wiegand | lpr | qr | ticket | manual`) — *how a vehicle was identified* — not
|
||||
a device/IP field. A raw button push has no vehicle identity. The device provenance lives in
|
||||
**`identity`** (e.g. `dingtian:<id> input:1/on`).
|
||||
### ⚠️ As-built vs. the table split (pending)
|
||||
|
||||
The current code records Dingtian **input (button) pushes** as `input_received` rows **in the
|
||||
signed chain** (with `lane` resolved via the `LaneMap`, `source` null, device provenance in
|
||||
`identity`). Per the 2026-06-15 split (above), a raw button press is **device telemetry** and
|
||||
belongs in **`device_events`**, *not* the signed ledger — only the business `vehicle_entry` it
|
||||
drives gets signed. So `input_received`-in-the-ledger is **transitional**; the pending refactor
|
||||
moves raw inputs to `device_events` and renames the chain table to `ledger_events`. (`LaneMap`
|
||||
lane-resolution and the "never stamp `lane: 0` for an unmapped device" rule carry over to whichever
|
||||
stream records the event.)
|
||||
|
||||
### ⚠️ Limitation: the log captures HOST-ORIGINATED actions only
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
type: concept
|
||||
tags: [parking, devices, monitoring, telemetry]
|
||||
sources: []
|
||||
updated: 2026-06-15
|
||||
status: open
|
||||
---
|
||||
|
||||
# Device Events (telemetry)
|
||||
|
||||
The **unsigned** operational record of what the hardware did and reported — distinct from the
|
||||
signed business [[append-only-event-chain|ledger]] (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_command` is 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 `lane_devices` instance; `lane` resolved via the same `LaneMap`
|
||||
as before. Stores raw device provenance.
|
||||
|
||||
## 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|adapter boundary]] 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]].
|
||||
Reference in New Issue
Block a user