TacidOS
DevelopersGitHub

Decision 0045

What is plugged in is a fact, with a role and the profile that grants access

Architecture decision record 0045: What is plugged in is a fact, with a role and the profile that grants access

Status
accepted
Date
2026-09-05

Context

The plan’s device center is a graph of what is attached — USB and serial devices, debug probes, their identifiers, interfaces, drivers, permissions and inferred capabilities — and a quiet notification on connection with “open” and “use with current project”. The facts had a flat list of USB devices: ids and names, no interfaces, no nodes, no idea what any of it was for. “What is plugged in” was a question the machine could not answer in its own terms, and “I see an ST-Link but you cannot open it” was a diagnosis it could not make.

The curated udev profiles (ADR-0023’s contribution on-ramp) already say which devices a person may reach. Read the other way round, they say what a device seen on the bus is asking for.

Prior decisions

ADR-0028 makes facts structured and read from the kernel, never parsed from shell output. ADR-0023 makes device rules curated profiles rather than free-form udev. ADR-0029 names the probes and boards Tacid is for.

Decision

system.devices is the graph, read from sysfs; a role and a profile are inferred from a table that mirrors the curated rules; tacid device shows it, and the rules model answers “what is plugged in”.

For every USB device the fact gives its ids, names and serial, its driver, and its interfaces — each with its class, its driver and the device nodes the kernel put under it, /dev/ttyACM0 or /dev/hidraw0, with the mode and group each node has right now, or nothing when the node is not there to be looked at. What a device is for comes from one table: the ST-Link, J-Link, Pi, Black Magic, FTDI, CP210x, CH340 and Nordic ids the profiles name, and any product that calls itself CMSIS-DAP; the table names the profile beside the role, so the answer to “why can I not open it” is one declaration away — tacid.hardware.udev.profiles = [ "stlink" ] — and the table can only name profiles that exist. A keyboard is an input device with no profile, because the kernel serves it without a rule; a hub is a hub.

The notification on connection, and “use with current project”, are the shell’s and the project tools’ to build, and this record does not claim them.

Consequences

  • tacid device reads the graph as one line per device; tacid ask "what is plugged in" is answered offline from the same fact.
  • The lifecycle test puts a USB keyboard on the machine’s bus and finds it, by id and by name, as an input device.
  • The role table and the udev profiles are two lists of the same devices, in two languages. A profile added without a row here is a grant the device center cannot point at; the contribution on-ramp for a probe is now the rule and the row together.

Alternatives rejected

  • Reading lsusb. Facts are read from the kernel, not parsed from a tool’s output.
  • Guessing capabilities from interface classes alone. A class says “CDC data”, not “this is the debug probe you flash with”; the ids do, and the profiles already list them.