- The specs index no longer carries a table mapping the three original spec names to the renumbered ones, and spec headers no longer record a feature branch or "first written as" name. - A task that pointed at research under the old 001 directory now points at its current path, so every reference resolves to a spec that exists. - The constitution's 1.4.1 amendment still records that the specs were split and renumbered, without listing the retired names.
4.1 KiB
Data Model: Midi Harbor
Feature: 001-service-and-clients | Date: 2026-09-20
This document defines the domain entities, their fields, relationships, validation rules, and
state transitions. It is the vocabulary shared by the daemon, the IPC contract, the CLI, and the
GUI. Types live in midi-harbor-core unless noted.
Each endpoint kind, peers, routes, connection state, counters, events, the configuration and capabilities are described in the spec they belong to.
1. Identity
Three different notions of identity must not be confused.
| Identity | Scope | Stability | Purpose |
|---|---|---|---|
EndpointId |
Midi Harbor's config | Permanent once assigned | What routes reference |
PlatformHandle |
One daemon run | Lost on restart or replug | What the OS API needs |
DeviceFingerprint |
Physical hardware | Survives unplug/replug | Re-binds hardware to its EndpointId |
EndpointId: an opaque UUID assigned at creation and never reused. Renaming an endpoint does
not change it (FR-004). This is the only identifier persisted in routes.
DeviceFingerprint: a composite key for physical hardware (FR-015e, R-013), carrying a
confidence level:
DeviceFingerprint {
unique_id: Option<i32> // CoreMIDI kMIDIPropertyUniqueID
usb_serial: Option<String> // Linux, from sysfs, when reported
manufacturer: Option<String>
model: Option<String>
name: String // always present, never sufficient alone
topology_path: Option<String> // stable per physical socket, not across sockets
}
MatchConfidence is Exact (unique id or USB serial matched), Probable (manufacturer + model +
name matched), or Ambiguous (name only, or several candidates matched). Only Exact and
Probable auto-rebind routes; Ambiguous surfaces a choice to the user rather than guessing
(R-013, FR-015g).
2. Endpoint
The central entity. Everything MIDI flows to or from is an Endpoint (FR-015b).
Endpoint {
id: EndpointId
name: String // user-facing, editable
kind: EndpointKind
enabled: bool // user intent, persisted
state: ConnectionState // runtime, not persisted
direction: Direction // Input | Output | Bidirectional
counters: TrafficCounters // runtime
created_at: Timestamp
}
EndpointKind is a tagged union carrying kind-specific data:
VirtualPort(VirtualPort)NetworkSession(NetworkSession)PhysicalDevice(PhysicalDevice)BluetoothDevice(BluetoothDevice)
Validation:
nameis 1–128 characters, no control characters, trimmed of surrounding whitespace.namemust be unique among endpoints of the same kind (FR-005). Virtual ports and network ports share one namespace, because other applications see a network port as a port of its name (FR-015h). Names may otherwise collide across kinds, since the platform namespaces them separately.enabledis user intent and is independent ofstate. A disabled endpoint is never connected; an enabled endpoint may still beDisconnectedorRetrying.
3. Endpoint kinds
Entity relationships
Configuration 1───* Endpoint
Endpoint 1───1 EndpointKind (VirtualPort | PhysicalDevice | NetworkSession | BluetoothDevice)
Endpoint 1───1 ConnectionState
Endpoint 1───1 TrafficCounters
Endpoint 1───* Event
Route *───1 Endpoint (as source)
Route *───1 Endpoint (as destination)
NetworkSession 0───1 Peer
PhysicalDevice 1───1 DeviceFingerprint
At runtime, routes reference endpoints by EndpointId, never by platform handle — which is why
replugging and rebooting leave routes intact.
In the stored file, routes reference endpoints by name, so the document can be edited by hand (R-011). Renaming an endpoint through the daemon rewrites every route naming it in the same operation, so connections survive renames as FR-004 requires. A name matching nothing leaves the route visible and marked broken (FR-035) rather than silently dropped.