| phase |
plan |
subsystem |
tags |
dependency_graph |
tech_stack |
key_files |
decisions |
metrics |
| 04-usb-manager-label-printing |
01 |
usb |
| usb |
| serial |
| goroutine |
| enumeration |
| reconnect |
|
| requires |
provides |
affects |
|
|
| internal/usb package |
| USBManager |
| DeviceSpec |
| DeviceEvent |
| Command |
|
| internal/api/router.go (future SSE integration) |
| main.go (future Manager.Start call) |
|
|
| added |
patterns |
| go.bug.st/serial v1.6.4 |
| github.com/creack/goselect v0.1.2 |
|
| goroutine-per-device |
| context+done-channel teardown |
| injectable test seams |
|
|
| created |
modified |
| internal/usb/device.go |
| internal/usb/enumerate.go |
| internal/usb/manager.go |
| internal/usb/device_test.go |
| internal/usb/manager_test.go |
| internal/usb/mock_port_test.go |
|
|
|
| VID/PID enumeration via system_profiler SPUSBDataType JSON + injectable sysProfilerCmd for tests; gracefully returns empty map on Linux/CI |
| serialPort interface abstraction allows test injection via noopSerialOpener without hardware |
| mockPort.Read() blocks on closeCh channel until Close() is called — mirrors real blocking serial Read behavior |
| Read buffer capped at 4096 bytes per T-04-01 threat mitigation before forwarding to command handler |
| pollInterval injected at newManagerForTest level (20ms in tests vs 2s default) for fast deterministic cycling |
|
| duration_minutes |
completed_date |
tasks_completed |
tasks_total |
files_created |
files_modified |
| 15 |
2026-04-10 |
2 |
2 |
6 |
2 |
|
Phase 4 Plan 01: USB Manager Summary
One-liner: Goroutine-per-device USB Manager with VID/PID enumeration via system_profiler, context+done-channel teardown, and injectable test seams — no hardware required for testing.
What Was Built
internal/usb/device.go
Core types: DeviceSpec, DeviceRole, DeviceState, DeviceEvent, Command, CommandType. KnownDevices registry maps "VID:PID" to DeviceSpec (PRT Qutie placeholder 0525:a4a7). ParseVIDPID() helper with error on malformed input.
internal/usb/enumerate.go
enumerateConnected() shells out to system_profiler SPUSBDataType -json, parses the USB device tree, normalises hex IDs (strips 0x prefix, lowercases), and cross-references with serial.GetPortsList() to return map[VIDPID]portPath containing only KnownDevices entries. Two injectable function vars (sysProfilerCmd, serialPortsListCmd) enable hermetic unit tests. Returns empty map with no error on Linux/CI (graceful degradation when system_profiler is unavailable).
internal/usb/manager.go
Manager owns all device goroutines. Poll loop calls enumerateFunc() every pollInterval, reconciles prev/current snapshots, spawns/stops deviceLoop goroutines. deviceLoop opens the serial port, runs an inner read goroutine (exits on ctx.Done() or port error), and handles CmdWrite/CmdClose commands. Goroutine teardown: child context cancelled → port.Close() called → read goroutine unblocked → done channel closed → poll loop proceeds. ErrDeviceNotConnected returned by Send() when device absent.
Threat mitigations applied
- T-04-01: Read buffer capped at 4096 bytes in
deviceLoop before forwarding to command handler.
Commits
| Task |
Commit |
Description |
| Task 1 |
f5b1d31 |
USB device types, KnownDevices registry, VID/PID enumeration |
| Task 2 |
82eaf6b |
USB Manager goroutine-per-device, poll loop, reconnect, leak-safe teardown |
Test Results
=== RUN TestKnownDevicesContainsPRTQutie PASS
=== RUN TestEnumerateConnectedMockOutput PASS
=== RUN TestDeviceSpecString PASS
=== RUN TestParseVIDPIDValid PASS
=== RUN TestParseVIDPIDInvalid PASS
=== RUN TestManagerStartSpawnsOneGoroutine PASS
=== RUN TestManagerDisconnectReconnectEvents PASS
=== RUN TestManagerStopGoroutineLeak PASS
=== RUN TestManagerSendToAbsentDevice PASS
=== RUN TestGoroutineStability PASS
PASS (race detector clean, 30s timeout)
Deviations from Plan
Auto-fixed Issues
None — plan executed exactly as written.
Additional Files
internal/usb/mock_port_test.go was added (not listed in plan's files_modified). The plan referenced newMockPort() in the test action spec but did not create a dedicated file for it — the mock helper needed to be in a separate _test.go file to avoid shipping test infrastructure in the production binary.
Known Stubs
| File |
Item |
Reason |
| internal/usb/device.go |
KnownDevices["0525:a4a7"] VID/PID placeholder |
Real VID/PID unknown until hardware arrives 2026-04-13 — update after characterization |
| internal/usb/manager.go |
Read bytes in deviceLoop are logged but not forwarded |
No upstream consumer yet; Phase 4 plan 03 (printer driver) will consume read events |
Threat Flags
None — no new network endpoints, auth paths, or schema changes introduced. USB serial I/O is local-only per T-04-03 acceptance.
Self-Check: PASSED
- internal/usb/device.go: FOUND
- internal/usb/enumerate.go: FOUND
- internal/usb/manager.go: FOUND
- internal/usb/device_test.go: FOUND
- internal/usb/manager_test.go: FOUND
- internal/usb/mock_port_test.go: FOUND
- Commit
f5b1d31: FOUND
- Commit
82eaf6b: FOUND