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.
This commit is contained in:
2026-06-15 12:51:21 +02:00
parent f5fd61984a
commit 59bfe2013f
5 changed files with 83 additions and 7 deletions
+9 -1
View File
@@ -48,7 +48,13 @@ function redactSecrets(config: Record<string, unknown>): Record<string, unknown>
return out;
}
export async function setupRoutes(app: FastifyInstance, db: Db): Promise<void> {
export async function setupRoutes(
app: FastifyInstance,
db: Db,
// Called after the set of assignments changes (assign/unassign) so the caller
// can refresh anything derived from it — e.g. the device id->lane map.
onAssignmentsChanged: () => void = () => {},
): Promise<void> {
registerBuiltinDrivers();
setDeviceLogSink((line) => app.log.info(line));
@@ -251,6 +257,7 @@ export async function setupRoutes(app: FastifyInstance, db: Db): Promise<void> {
enabled: true,
};
await db.insert(laneDevices).values(row);
onAssignmentsChanged(); // refresh derived state (device->lane map)
// Don't echo device secrets back (push Digest password, web-UI login, …).
return reply.code(201).send({
...row,
@@ -281,6 +288,7 @@ export async function setupRoutes(app: FastifyInstance, db: Db): Promise<void> {
.get();
if (!existing) return reply.code(404).send({ error: "no such device assignment" });
await db.delete(laneDevices).where(eq(laneDevices.id, req.params.id));
onAssignmentsChanged(); // refresh derived state (device->lane map)
app.log.info(`unassigned device ${req.params.id} (${existing.category}/${existing.driverId}, lane ${existing.lane})`);
return reply.code(204).send();
},