- Route every reserved-state mutation through withInventoryWrite; the
lease insert path now broadcasts so extended subscribers see the busy
transition immediately instead of waiting for the next mutation. Folds
cleanupExpiredLocked changes into the broadcast decision on every
IssueLease early-reject path. Rename the field to inventory and force
read/write/write-quiet sites through dedicated accessors so future
callers cannot bypass the invariant.
- Mirror the linux clone-then-swap pattern in darwinExportHost.Reconcile
via cloneDarwinExport, so the ledger's unlocked Snapshot / LeaseCheck
reads never observe a half-mutated stale flag. Documented as
docs/adr/0001-export-pointer-immutability.md and on the Export
interface; both hosts now share the applyStaleClones helper.
- Rewrite 27 if (_, )?err := …; err != nil sites to assign-then-check
per .claude/rules/go-syntax.md.
- Delete parse / builder / tautological tests forbidden by
.claude/rules/code-test.md (option/usbip_test.go,
iso_scheduler_test.go, usbhost_darwin_status_test.go).
- Tag nine more usbip files with linux || (darwin && cgo); fixes
pre-existing windows / android lint failures because the protocol
types were only consumed by tagged code.
control_protocol.go was ungated but referenced ExportLeaseIdentity
from host.go (linux || (darwin && cgo)), breaking the stub-register
contract on Windows and darwin && !cgo. Move serverImportLease and
importLeaseTTL into export_ledger.go alongside their only consumers.
Three independent regressions that silently degraded the USB/IP feature:
- Darwin: when a busy device re-enumerated (same busid, new IOUSBHost
registry ID), Reconcile marked the export stale but never captured
the replacement, and nothing reconciled after FinishImport — the
device only reappeared on an unrelated future topology event. Now
Reconcile records the pending registry ID on the stale entry,
FinishImport rehydrates inline, and the server triggers a
reconcileAndBroadcast after release as a safety net.
- DEVLIST: SerialString and entryDeviceKey expected a "\0serial=..."
trailer in the 256-byte Path field, but encodePathField never wrote
one, so serial-based matching silently failed against plain usbipd
peers and against sing-box peers that fell back to DEVLIST polling.
encodePathField now writes the trailer (wire-compatible with kernel
usbip-utils, which NUL-terminate the path) and ReadOpRepDevListBody
populates DeviceEntry.Serial on decode.
- Control: any write/read I/O failure during the CONTROL handshake was
wrapped as errControlUnsupported, so a single TCP RST or 5s deadline
on startup permanently downgraded the client to 5-second DEVLIST
polling. Now only io.EOF on the ACK read and frame-validation
failures stay unsupported; other I/O failures retry with
exponential backoff (1s→30s) up to 3 consecutive transients before
committing to the polling fallback.
Apply the <10-LoC / <3-non-test-caller heuristic across the package:
inline writeControlFrame, removeLeaseWaiter, vhciAttach, vhciHubForSpeed,
applyControlSnapshot, applyRemoteEntries, shouldRetryBusID, entrySerial,
and the trivial darwin/linux import-host constructors at their call
sites.
Delete the darwinClientSession wrapper -- darwinVirtualController already
satisfies DataSession, so attach the Description() method to it directly
and return the controller from darwinImportHost.Attach.
Fold the 11-line data_session.go into host.go beside AttachedSession,
which embeds DataSession.
Inline thin pass-through helpers (< 10 lines, < 3 non-test callers) and
the writeSysfs path wrappers; delete the test-only vhciUsedPorts plus
its singleflight machinery and the never-called clientAssignment.IsActive.
Remove four private single-impl interfaces (usbEventListener,
darwinUSBHostDeviceWatch, darwinServerDataDevice, darwinEndpointStateMachine)
and both function-table DI seams (usbipOps, darwinServerOps); production calls
the underlying functions directly. Inline ~25 single-call helpers and fuse
subordinate routines into their sole caller. Collapse single-field helper
structs into their underlying types. Fold server_linux.go/server_darwin.go
into host_linux.go/host_darwin.go.
Delete change-detector tests per .claude/rules/code-test.md: parse round-trip,
mirror, input-validation, and builder-property suites that never exercised
real syscalls, network, or cgo. Six test files removed; linux_test.go slimmed
to three real-system tests; darwin_integration_test.go retains the six
real-cgo tests. Surviving suite: 16 tests that all exercise real OS APIs or
spawn the official Linux usbip server.
Net: -5100 LoC (~37% of the package).