type-contracts.md
markdown
| 1 | # Muse Hub — Type Contracts Reference |
| 2 | |
| 3 | > Updated: 2026-03-17 | Covers every named entity in the Muse Hub surface: |
| 4 | > MIDI type aliases, JSON wire types, MCP protocol types, Pydantic API models, |
| 5 | > auth tokens, SSE event hierarchy, SQLAlchemy ORM models. |
| 6 | > `Any` and bare `list` / `dict` (without type arguments) do not appear in any |
| 7 | > production file. Every type boundary is named. The mypy strict ratchet |
| 8 | > enforces zero violations on every CI run across 111 source files. |
| 9 | |
| 10 | --- |
| 11 | |
| 12 | ## Table of Contents |
| 13 | |
| 14 | 1. [Design Philosophy](#design-philosophy) |
| 15 | 2. [MIDI Type Aliases (`contracts/midi_types.py`)](#midi-type-aliases) |
| 16 | 3. [JSON Type Aliases (`contracts/json_types.py`)](#json-type-aliases) |
| 17 | 4. [JSON Wire TypedDicts (`contracts/json_types.py`)](#json-wire-typeddicts) |
| 18 | 5. [MCP Protocol Types (`contracts/mcp_types.py`)](#mcp-protocol-types) |
| 19 | 6. [Pydantic Base Types (`contracts/pydantic_types.py`, `models/base.py`)](#pydantic-base-types) |
| 20 | 7. [Auth Types (`auth/tokens.py`)](#auth-types) |
| 21 | 8. [Protocol Events (`protocol/events.py`)](#protocol-events) |
| 22 | 9. [Protocol HTTP Responses (`protocol/responses.py`)](#protocol-http-responses) |
| 23 | 10. [API Models (`models/musehub.py`)](#api-models) |
| 24 | 11. [Database ORM Models (`db/`)](#database-orm-models) |
| 25 | 12. [Entity Hierarchy](#entity-hierarchy) |
| 26 | 13. [Entity Graphs (Mermaid)](#entity-graphs-mermaid) |
| 27 | |
| 28 | --- |
| 29 | |
| 30 | ## Design Philosophy |
| 31 | |
| 32 | Every entity in this codebase follows five rules: |
| 33 | |
| 34 | 1. **No `Any`. No bare `object`. Ever.** Both collapse type safety for downstream |
| 35 | callers. Every boundary is typed with a concrete named entity — `TypedDict`, |
| 36 | `dataclass`, Pydantic model, or a specific union. The CI mypy strict ratchet |
| 37 | enforces zero violations. |
| 38 | |
| 39 | 2. **No covariance in collection aliases.** `dict[str, str]` and `list[str]` |
| 40 | are used directly. If a return mixes value types, a `TypedDict` names that |
| 41 | shape instead of a `dict[str, str | int]`. |
| 42 | |
| 43 | 3. **Boundaries own coercion.** When external data arrives (JSON over HTTP, |
| 44 | bytes from the database, MIDI off the wire), the boundary module coerces to |
| 45 | the canonical internal type. Downstream code always sees clean types. |
| 46 | |
| 47 | 4. **Wire-format TypedDicts for JSON serialisation, Pydantic models for HTTP.** |
| 48 | `TokenClaims`, `NoteDict`, `MCPRequest` etc. are JSON-serialisable and |
| 49 | used at IO boundaries. Pydantic `CamelModel` subclasses are used for all |
| 50 | FastAPI route return types and request bodies. |
| 51 | |
| 52 | 5. **No `# type: ignore`. Fix the underlying error instead.** The one designated |
| 53 | exception is the `json_list()` coercion boundary — a single |
| 54 | `type: ignore[arg-type]` inside the implementation body of that helper. |
| 55 | |
| 56 | ### Banned → Use instead |
| 57 | |
| 58 | | Banned | Use instead | |
| 59 | |--------|-------------| |
| 60 | | `Any` | `TypedDict`, `dataclass`, specific union | |
| 61 | | `object` | The actual type or a constrained union | |
| 62 | | `list` (bare) | `list[X]` with concrete element type | |
| 63 | | `dict` (bare) | `dict[K, V]` with concrete key/value types | |
| 64 | | `dict[str, X]` with known keys | `TypedDict` — name the keys | |
| 65 | | `Optional[X]` | `X \| None` | |
| 66 | | Legacy `List`, `Dict`, `Set`, `Tuple` | Lowercase builtins | |
| 67 | | `Union[A, B]` | `A \| B` | |
| 68 | | `Type[X]` | `type[X]` | |
| 69 | | `cast(T, x)` | Fix the callee to return `T` | |
| 70 | | `# type: ignore` | Fix the underlying type error | |
| 71 | |
| 72 | --- |
| 73 | |
| 74 | ## MIDI Type Aliases |
| 75 | |
| 76 | **Path:** `musehub/contracts/midi_types.py` |
| 77 | |
| 78 | Constrained `int` and `float` aliases via `Annotated[int, Field(...)]`. |
| 79 | Every MIDI boundary uses these instead of bare `int` so that range constraints |
| 80 | are part of the type signature and enforced by Pydantic at validation time. |
| 81 | |
| 82 | | Alias | Base | Constraint | Description | |
| 83 | |-------|------|------------|-------------| |
| 84 | | `MidiPitch` | `int` | 0–127 | MIDI note number (C-1=0, Middle C=60, G9=127) | |
| 85 | | `MidiVelocity` | `int` | 0–127 | Note velocity; 0=note-off, 1–127=audible | |
| 86 | | `MidiChannel` | `int` | 0–15 | Zero-indexed MIDI channel; drums=9 | |
| 87 | | `MidiCC` | `int` | 0–127 | MIDI Control Change controller number | |
| 88 | | `MidiCCValue` | `int` | 0–127 | MIDI Control Change value | |
| 89 | | `MidiAftertouchValue` | `int` | 0–127 | Channel or poly aftertouch pressure | |
| 90 | | `MidiGMProgram` | `int` | 0–127 | General MIDI program / patch number (0-indexed) | |
| 91 | | `MidiPitchBend` | `int` | −8192–8191 | 14-bit signed pitch bend; 0=centre | |
| 92 | | `MidiBPM` | `int` | 20–300 | Tempo in beats per minute (always integer) | |
| 93 | | `BeatPosition` | `float` | ≥ 0.0 | Absolute beat position; fractional allowed | |
| 94 | | `BeatDuration` | `float` | > 0.0 | Duration in beats; must be strictly positive | |
| 95 | | `ArrangementBeat` | `int` | ≥ 0 | Bar-aligned beat offset for section-level timing | |
| 96 | | `ArrangementDuration` | `int` | ≥ 1 | Section duration in beats (bars × numerator) | |
| 97 | | `Bars` | `int` | ≥ 1 | Bar count; always a positive integer | |
| 98 | |
| 99 | --- |
| 100 | |
| 101 | ## JSON Type Aliases |
| 102 | |
| 103 | **Path:** `musehub/contracts/json_types.py` |
| 104 | |
| 105 | Type aliases for recursive and domain-specific JSON shapes. |
| 106 | |
| 107 | | Alias | Definition | Description | |
| 108 | |-------|-----------|-------------| |
| 109 | | `JSONScalar` | `str \| int \| float \| bool \| None` | A JSON leaf value with no recursive structure | |
| 110 | | `JSONValue` | `str \| int \| float \| bool \| None \| list["JSONValue"] \| dict[str, "JSONValue"]` | Recursive JSON value — use sparingly, never in Pydantic models | |
| 111 | | `JSONObject` | `dict[str, JSONValue]` | A JSON object with an unknown key set | |
| 112 | | `InternalNoteDict` | `NoteDict` | Alias for `NoteDict` on the snake_case storage path | |
| 113 | | `RegionNotesMap` | `dict[str, list[NoteDict]]` | Maps `region_id` → ordered list of MIDI notes | |
| 114 | | `RegionCCMap` | `dict[str, list[CCEventDict]]` | Maps `region_id` → ordered list of MIDI CC events | |
| 115 | | `RegionPitchBendMap` | `dict[str, list[PitchBendDict]]` | Maps `region_id` → ordered list of MIDI pitch bend events | |
| 116 | | `RegionAftertouchMap` | `dict[str, list[AftertouchDict]]` | Maps `region_id` → ordered list of MIDI aftertouch events | |
| 117 | | `EventJsonSchema` | `dict[str, JSONValue]` | JSON Schema dict for a single event type, as produced by `model_json_schema()` | |
| 118 | | `EventSchemaMap` | `dict[str, EventJsonSchema]` | Maps `event_type` → its JSON Schema; returned by `/protocol/events.json` | |
| 119 | |
| 120 | --- |
| 121 | |
| 122 | ## JSON Wire TypedDicts |
| 123 | |
| 124 | **Path:** `musehub/contracts/json_types.py` |
| 125 | |
| 126 | These are the MIDI note and event shapes exchanged across service boundaries. |
| 127 | All use `total=False` where the producer may omit fields, and `Required[T]` |
| 128 | to mark fields that must always be present. |
| 129 | |
| 130 | ### `NoteDict` |
| 131 | |
| 132 | `TypedDict (total=False)` — A single MIDI note. Accepts both camelCase (wire |
| 133 | format) and snake_case (internal storage) to avoid a transform boundary. |
| 134 | |
| 135 | | Field | Type | Description | |
| 136 | |-------|------|-------------| |
| 137 | | `pitch` | `MidiPitch` | MIDI pitch number | |
| 138 | | `velocity` | `MidiVelocity` | Note velocity | |
| 139 | | `channel` | `MidiChannel` | MIDI channel | |
| 140 | | `startBeat` | `BeatPosition` | Onset beat (camelCase wire) | |
| 141 | | `durationBeats` | `BeatDuration` | Duration in beats (camelCase wire) | |
| 142 | | `noteId` | `str` | Note UUID (camelCase wire) | |
| 143 | | `trackId` | `str` | Parent track UUID (camelCase wire) | |
| 144 | | `regionId` | `str` | Parent region UUID (camelCase wire) | |
| 145 | | `start_beat` | `BeatPosition` | Onset beat (snake_case storage) | |
| 146 | | `duration_beats` | `BeatDuration` | Duration in beats (snake_case storage) | |
| 147 | | `note_id` | `str` | Note UUID (snake_case storage) | |
| 148 | | `track_id` | `str` | Parent track UUID (snake_case storage) | |
| 149 | | `region_id` | `str` | Parent region UUID (snake_case storage) | |
| 150 | | `layer` | `str` | Optional layer grouping (e.g. `"melody"`) | |
| 151 | |
| 152 | ### `CCEventDict` |
| 153 | |
| 154 | `TypedDict` — A single MIDI Control Change event. |
| 155 | |
| 156 | | Field | Type | |
| 157 | |-------|------| |
| 158 | | `cc` | `MidiCC` | |
| 159 | | `beat` | `BeatPosition` | |
| 160 | | `value` | `MidiCCValue` | |
| 161 | |
| 162 | ### `PitchBendDict` |
| 163 | |
| 164 | `TypedDict` — A single MIDI pitch bend event. |
| 165 | |
| 166 | | Field | Type | |
| 167 | |-------|------| |
| 168 | | `beat` | `BeatPosition` | |
| 169 | | `value` | `MidiPitchBend` | |
| 170 | |
| 171 | ### `AftertouchDict` |
| 172 | |
| 173 | `TypedDict (total=False)` — MIDI aftertouch (channel or poly). |
| 174 | |
| 175 | | Field | Type | Required | |
| 176 | |-------|------|----------| |
| 177 | | `beat` | `BeatPosition` | Yes | |
| 178 | | `value` | `MidiAftertouchValue` | Yes | |
| 179 | | `pitch` | `MidiPitch` | No (poly only) | |
| 180 | |
| 181 | ### `SectionDict` |
| 182 | |
| 183 | `TypedDict (total=False)` — A composition section (verse, chorus, bridge, etc.). |
| 184 | Used by the analysis service to describe structural section metadata. |
| 185 | |
| 186 | | Field | Type | |
| 187 | |-------|------| |
| 188 | | `name` | `str` | |
| 189 | | `start_beat` | `float` | |
| 190 | | `length_beats` | `float` | |
| 191 | | `description` | `str` | |
| 192 | | `per_track_description` | `dict[str, str]` | |
| 193 | |
| 194 | ### Conversion helpers |
| 195 | |
| 196 | | Function | Signature | Description | |
| 197 | |----------|-----------|-------------| |
| 198 | | `is_note_dict(v)` | `(JSONValue) -> TypeGuard[NoteDict]` | Narrows `JSONValue` to `NoteDict` in list comprehensions | |
| 199 | | `jfloat(v, default)` | `(JSONValue, float) -> float` | Safe float extraction from `JSONValue` | |
| 200 | | `jint(v, default)` | `(JSONValue, int) -> int` | Safe int extraction from `JSONValue` | |
| 201 | | `json_list(items)` | overloaded | Coerces a `list[TypedDict]` to `list[JSONValue]` at insertion boundaries | |
| 202 | |
| 203 | --- |
| 204 | |
| 205 | ## MCP Protocol Types |
| 206 | |
| 207 | **Path:** `musehub/contracts/mcp_types.py` |
| 208 | |
| 209 | Typed shapes for the Model Context Protocol JSON-RPC 2.0 interface. TypedDicts |
| 210 | cover the wire format; Pydantic models (`MCPToolDefWire` etc.) are used for |
| 211 | FastAPI response serialisation. |
| 212 | |
| 213 | ### Type Aliases |
| 214 | |
| 215 | | Alias | Definition | |
| 216 | |-------|-----------| |
| 217 | | `MCPResponse` | `MCPSuccessResponse \| MCPErrorResponse` | |
| 218 | | `MCPMethodResponse` | `MCPInitializeResponse \| MCPToolsListResponse \| MCPCallResponse \| MCPSuccessResponse \| MCPErrorResponse` | |
| 219 | |
| 220 | ### Request / Response TypedDicts |
| 221 | |
| 222 | | Name | Kind | Description | |
| 223 | |------|------|-------------| |
| 224 | | `MCPRequest` | `total=False` | Incoming JSON-RPC 2.0 message from an MCP client | |
| 225 | | `MCPSuccessResponse` | required | JSON-RPC 2.0 success response | |
| 226 | | `MCPErrorDetail` | `total=False` | The `error` object inside a JSON-RPC error response | |
| 227 | | `MCPErrorResponse` | required | JSON-RPC 2.0 error response | |
| 228 | | `MCPInitializeParams` | `total=False` | Params for the `initialize` method | |
| 229 | | `MCPInitializeResult` | required | Result body for `initialize` | |
| 230 | | `MCPInitializeResponse` | required | Full response for `initialize` | |
| 231 | | `MCPToolsListResult` | required | Result body for `tools/list` | |
| 232 | | `MCPToolsListResponse` | required | Full response for `tools/list` | |
| 233 | | `MCPCallResult` | `total=False` | Result body for `tools/call` | |
| 234 | | `MCPCallResponse` | required | Full response for `tools/call` | |
| 235 | |
| 236 | ### Tool Definition TypedDicts |
| 237 | |
| 238 | | Name | Kind | Description | |
| 239 | |------|------|-------------| |
| 240 | | `MCPPropertyDef` | `total=False` | JSON Schema definition for a single tool property | |
| 241 | | `MCPInputSchema` | `total=False` | JSON Schema for a tool's accepted arguments | |
| 242 | | `MCPToolDef` | `total=False` | Complete definition of an MCP tool | |
| 243 | | `MCPContentBlock` | required | Content block in a tool result (`type`, `text`) | |
| 244 | |
| 245 | ### Capability TypedDicts |
| 246 | |
| 247 | | Name | Kind | Description | |
| 248 | |------|------|-------------| |
| 249 | | `MCPToolsCapability` | `total=False` | `tools` entry in `MCPCapabilities` | |
| 250 | | `MCPResourcesCapability` | `total=False` | `resources` entry in `MCPCapabilities` | |
| 251 | | `MCPCapabilities` | `total=False` | Server capabilities advertised during `initialize` | |
| 252 | | `MCPServerInfo` | required | Server info returned in `initialize` responses | |
| 253 | | `MCPCapabilitiesResult` | required | Capability block in `initialize` result | |
| 254 | | `MCPToolCallParams` | required | Params for `tools/call` | |
| 255 | |
| 256 | ### DAW ↔ MCP Bridge TypedDicts |
| 257 | |
| 258 | | Name | Kind | Description | |
| 259 | |------|------|-------------| |
| 260 | | `DAWToolCallMessage` | required | Message sent from MCP server to a DAW client over WebSocket | |
| 261 | | `DAWToolResponse` | `total=False` | Response sent from the DAW back after tool execution | |
| 262 | |
| 263 | ### Pydantic Wire Models (FastAPI) |
| 264 | |
| 265 | | Name | Description | |
| 266 | |------|-------------| |
| 267 | | `MCPPropertyDefWire` | Pydantic-safe JSON Schema property for FastAPI responses | |
| 268 | | `MCPInputSchemaWire` | Pydantic-safe tool input schema for FastAPI responses | |
| 269 | | `MCPToolDefWire` | Pydantic-safe tool definition for FastAPI route return types | |
| 270 | |
| 271 | --- |
| 272 | |
| 273 | ## Pydantic Base Types |
| 274 | |
| 275 | **Path:** `musehub/contracts/pydantic_types.py`, `musehub/models/base.py` |
| 276 | |
| 277 | ### `PydanticJson` |
| 278 | |
| 279 | `RootModel[str | int | float | bool | None | list["PydanticJson"] | dict[str, "PydanticJson"]]` |
| 280 | |
| 281 | The only safe recursive JSON field type for Pydantic models. Used wherever |
| 282 | a model field accepts an arbitrary JSON value (e.g. tool call parameters or |
| 283 | protocol introspection schemas). Replaces `dict[str, Any]` at every Pydantic |
| 284 | boundary. |
| 285 | |
| 286 | **Helpers:** |
| 287 | |
| 288 | | Function | Description | |
| 289 | |----------|-------------| |
| 290 | | `wrap(v: JSONValue) -> PydanticJson` | Convert a `JSONValue` to a `PydanticJson` instance | |
| 291 | | `unwrap(p: PydanticJson) -> JSONValue` | Convert a `PydanticJson` back to a `JSONValue` | |
| 292 | | `wrap_dict(d: dict[str, JSONValue]) -> dict[str, PydanticJson]` | Wrap each value in a dict | |
| 293 | |
| 294 | ### `CamelModel` |
| 295 | |
| 296 | `BaseModel` subclass. All Pydantic API models (request bodies and response |
| 297 | types) inherit from this. Configures: |
| 298 | |
| 299 | - `alias_generator = to_camel` — fields are serialised to camelCase on the wire. |
| 300 | - `populate_by_name = True` — allows snake_case names in Python code. |
| 301 | - `extra = "ignore"` — unknown fields from clients are silently dropped. |
| 302 | |
| 303 | --- |
| 304 | |
| 305 | ## Auth Types |
| 306 | |
| 307 | **Path:** `musehub/auth/tokens.py` |
| 308 | |
| 309 | ### `TokenClaims` |
| 310 | |
| 311 | `TypedDict (total=False)` — Decoded JWT payload returned by `validate_access_code`. |
| 312 | `type`, `iat`, and `exp` are always present (`Required`); `sub` and `role` are |
| 313 | optional claims added by the issuer. |
| 314 | |
| 315 | | Field | Type | Required | |
| 316 | |-------|------|----------| |
| 317 | | `type` | `str` | Yes | |
| 318 | | `iat` | `int` | Yes | |
| 319 | | `exp` | `int` | Yes | |
| 320 | | `sub` | `str` | No | |
| 321 | | `role` | `str` | No | |
| 322 | |
| 323 | --- |
| 324 | |
| 325 | ## Protocol Events |
| 326 | |
| 327 | **Path:** `musehub/protocol/events.py` |
| 328 | |
| 329 | Protocol events are Pydantic `CamelModel` subclasses of `MuseEvent`, which |
| 330 | provides `type`, `seq`, and `protocol_version` on every payload. Muse Hub |
| 331 | defines two concrete event types — both in the MCP relay path. |
| 332 | |
| 333 | ### Base Class |
| 334 | |
| 335 | | Name | Kind | Description | |
| 336 | |------|------|-------------| |
| 337 | | `MuseEvent` | Pydantic (CamelModel) | Base class: `type: str`, `seq: int = -1`, `protocol_version: str` | |
| 338 | |
| 339 | ### Concrete Event Types |
| 340 | |
| 341 | | Event | `type` Literal | Description | |
| 342 | |-------|---------------|-------------| |
| 343 | | `MCPMessageEvent` | `"mcp.message"` | MCP tool-call message relayed over SSE; `payload: dict[str, object]` | |
| 344 | | `MCPPingEvent` | `"mcp.ping"` | MCP SSE keepalive heartbeat | |
| 345 | |
| 346 | The event registry (`protocol/registry.py`) maps these two type strings to |
| 347 | their model classes and is frozen at import time. |
| 348 | |
| 349 | --- |
| 350 | |
| 351 | ## Protocol HTTP Responses |
| 352 | |
| 353 | **Path:** `musehub/protocol/responses.py` |
| 354 | |
| 355 | Pydantic response models for the four protocol introspection endpoints. |
| 356 | Fields use camelCase by declaration to match the wire format. |
| 357 | |
| 358 | | Name | Route | Description | |
| 359 | |------|-------|-------------| |
| 360 | | `ProtocolInfoResponse` | `GET /protocol` | Version, hash, and registered event type list | |
| 361 | | `ProtocolEventsResponse` | `GET /protocol/events.json` | JSON Schema per event type | |
| 362 | | `ProtocolToolsResponse` | `GET /protocol/tools.json` | All registered MCP tool definitions | |
| 363 | | `ProtocolSchemaResponse` | `GET /protocol/schema.json` | Unified snapshot — version + hash + events + tools | |
| 364 | |
| 365 | The protocol hash (`protocol/hash.py`) is a SHA-256 over the serialised event |
| 366 | schemas and tool schemas, computed deterministically at request time. |
| 367 | |
| 368 | --- |
| 369 | |
| 370 | ## API Models |
| 371 | |
| 372 | **Path:** `musehub/models/musehub.py` |
| 373 | |
| 374 | All are Pydantic `CamelModel` subclasses. Organized by domain feature. |
| 375 | |
| 376 | ### Git / VCS |
| 377 | |
| 378 | | Name | Description | |
| 379 | |------|-------------| |
| 380 | | `CommitInput` | A single commit in a push payload | |
| 381 | | `ObjectInput` | A binary object in a push payload | |
| 382 | | `PushRequest` | Body for `POST /repos/{id}/push` | |
| 383 | | `PushResponse` | Push confirmation with new branch head | |
| 384 | | `PullRequest` | Body for `POST /repos/{id}/pull` | |
| 385 | | `ObjectResponse` | Binary object returned in a pull response | |
| 386 | | `PullResponse` | Pull response — missing commits and objects | |
| 387 | |
| 388 | ### Repositories |
| 389 | |
| 390 | | Name | Description | |
| 391 | |------|-------------| |
| 392 | | `CreateRepoRequest` | Repository creation wizard body | |
| 393 | | `RepoResponse` | Wire representation of a Muse Hub repo | |
| 394 | | `TransferOwnershipRequest` | Transfer repo to another user | |
| 395 | | `RepoListResponse` | Paginated list of repos | |
| 396 | | `RepoStatsResponse` | Aggregated commit / branch / release counts | |
| 397 | |
| 398 | ### Branches |
| 399 | |
| 400 | | Name | Description | |
| 401 | |------|-------------| |
| 402 | | `BranchResponse` | Branch name and head commit pointer | |
| 403 | | `BranchListResponse` | Paginated list of branches | |
| 404 | | `BranchDivergenceScores` | Five-dimensional musical divergence scores | |
| 405 | | `BranchDetailResponse` | Branch with ahead/behind counts and divergence | |
| 406 | | `BranchDetailListResponse` | List of branches with detail | |
| 407 | |
| 408 | ### Commits and Tags |
| 409 | |
| 410 | | Name | Description | |
| 411 | |------|-------------| |
| 412 | | `CommitResponse` | Wire representation of a pushed commit | |
| 413 | | `CommitListResponse` | Paginated list of commits | |
| 414 | | `TagResponse` | A single tag entry | |
| 415 | | `TagListResponse` | All tags grouped by namespace | |
| 416 | |
| 417 | ### Issues |
| 418 | |
| 419 | | Name | Description | |
| 420 | |------|-------------| |
| 421 | | `IssueCreate` | Create issue body | |
| 422 | | `IssueUpdate` | Partial update body | |
| 423 | | `IssueResponse` | Wire representation of an issue | |
| 424 | | `IssueListResponse` | Paginated list of issues | |
| 425 | | `MusicalRef` | Parsed musical context reference (e.g. `track:bass`) | |
| 426 | | `IssueCommentCreate` | Create comment body | |
| 427 | | `IssueCommentResponse` | Wire representation of a comment | |
| 428 | | `IssueCommentListResponse` | Threaded discussion on an issue | |
| 429 | | `IssueAssignRequest` | Assign or unassign a user | |
| 430 | | `IssueLabelAssignRequest` | Replace the label list on an issue | |
| 431 | |
| 432 | ### Milestones |
| 433 | |
| 434 | | Name | Description | |
| 435 | |------|-------------| |
| 436 | | `MilestoneCreate` | Create milestone body | |
| 437 | | `MilestoneResponse` | Wire representation of a milestone | |
| 438 | | `MilestoneListResponse` | List of milestones | |
| 439 | |
| 440 | ### Pull Requests |
| 441 | |
| 442 | | Name | Description | |
| 443 | |------|-------------| |
| 444 | | `PRCreate` | Create PR body | |
| 445 | | `PRResponse` | Wire representation of a pull request | |
| 446 | | `PRListResponse` | Paginated list of pull requests | |
| 447 | | `PRMergeRequest` | Merge strategy selection body | |
| 448 | | `PRMergeResponse` | Merge confirmation | |
| 449 | | `PRDiffDimensionScore` | Per-dimension musical change score | |
| 450 | | `PRDiffResponse` | Musical diff between PR branches | |
| 451 | | `PRCommentCreate` | PR review comment body (supports four targeting granularities) | |
| 452 | | `PRCommentResponse` | Wire representation of a PR review comment | |
| 453 | | `PRCommentListResponse` | Threaded list of PR comments | |
| 454 | | `PRReviewerRequest` | Request reviewers | |
| 455 | | `PRReviewCreate` | Submit a formal review (approve / request_changes / comment) | |
| 456 | | `PRReviewResponse` | Wire representation of a review | |
| 457 | | `PRReviewListResponse` | List of reviews for a PR | |
| 458 | |
| 459 | ### Releases |
| 460 | |
| 461 | | Name | Description | |
| 462 | |------|-------------| |
| 463 | | `ReleaseCreate` | Create release body | |
| 464 | | `ReleaseDownloadUrls` | Structured download package URLs | |
| 465 | | `ReleaseResponse` | Wire representation of a release | |
| 466 | | `ReleaseListResponse` | List of releases | |
| 467 | | `ReleaseAssetCreate` | Attach asset to release | |
| 468 | | `ReleaseAssetResponse` | Wire representation of an asset | |
| 469 | | `ReleaseAssetListResponse` | Assets for a release | |
| 470 | | `ReleaseAssetDownloadCount` | Per-asset download count | |
| 471 | | `ReleaseDownloadStatsResponse` | Download counts for all assets | |
| 472 | |
| 473 | ### Profile and Social |
| 474 | |
| 475 | | Name | Description | |
| 476 | |------|-------------| |
| 477 | | `ProfileUpdateRequest` | Update profile body | |
| 478 | | `ProfileRepoSummary` | Compact repo summary on a profile page | |
| 479 | | `ProfileResponse` | Full wire representation of a user profile | |
| 480 | | `ContributorCredits` | Single contributor's credit record | |
| 481 | | `CreditsResponse` | Full credits roll for a repo | |
| 482 | | `StarResponse` | Star added / removed confirmation | |
| 483 | | `ContributionDay` | One day in the contribution heatmap | |
| 484 | |
| 485 | ### Discovery and Search |
| 486 | |
| 487 | | Name | Description | |
| 488 | |------|-------------| |
| 489 | | `ExploreRepoResult` | Public repo card on the explore page | |
| 490 | | `ExploreResponse` | Paginated discover response | |
| 491 | | `SearchCommitMatch` | Single commit returned by an in-repo search | |
| 492 | | `SearchResponse` | Response for all four in-repo search modes | |
| 493 | | `GlobalSearchCommitMatch` | Commit match in a cross-repo search | |
| 494 | | `GlobalSearchRepoGroup` | All matches for one repo in a cross-repo search | |
| 495 | | `GlobalSearchResult` | Top-level cross-repo search response | |
| 496 | |
| 497 | ### Timeline, DAG, and Analytics |
| 498 | |
| 499 | | Name | Description | |
| 500 | |------|-------------| |
| 501 | | `TimelineCommitEvent` | A commit plotted on the timeline | |
| 502 | | `TimelineEmotionEvent` | Emotion-vector data point on the timeline | |
| 503 | | `TimelineSectionEvent` | Detected section change marker | |
| 504 | | `TimelineTrackEvent` | Track addition or removal event | |
| 505 | | `TimelineResponse` | Chronological musical evolution timeline | |
| 506 | | `DivergenceDimensionResponse` | Per-dimension divergence score | |
| 507 | | `DivergenceResponse` | Full musical divergence report between two branches | |
| 508 | | `CommitDiffDimensionScore` | Per-dimension change score vs parent | |
| 509 | | `CommitDiffSummaryResponse` | Multi-dimensional diff summary | |
| 510 | | `DagNode` | Single commit node in the DAG | |
| 511 | | `DagEdge` | Directed edge in the commit DAG | |
| 512 | | `DagGraphResponse` | Topologically sorted commit graph | |
| 513 | |
| 514 | ### Webhooks |
| 515 | |
| 516 | | Name | Description | |
| 517 | |------|-------------| |
| 518 | | `WebhookCreate` | Create webhook body | |
| 519 | | `WebhookResponse` | Wire representation of a webhook | |
| 520 | | `WebhookListResponse` | List of webhooks | |
| 521 | | `WebhookDeliveryResponse` | Single delivery attempt | |
| 522 | | `WebhookDeliveryListResponse` | Delivery history | |
| 523 | | `WebhookRedeliverResponse` | Redeliver confirmation | |
| 524 | | `PushEventPayload` | TypedDict — push event webhook payload | |
| 525 | | `IssueEventPayload` | TypedDict — issue event webhook payload | |
| 526 | | `PullRequestEventPayload` | TypedDict (total=False) — PR event webhook payload | |
| 527 | | `WebhookEventPayload` | TypeAlias — `PushEventPayload \| IssueEventPayload \| PullRequestEventPayload` | |
| 528 | |
| 529 | ### Context |
| 530 | |
| 531 | | Name | Description | |
| 532 | |------|-------------| |
| 533 | | `MuseHubContextCommitInfo` | Minimal commit metadata in a context document | |
| 534 | | `MuseHubContextHistoryEntry` | Ancestor commit in evolutionary history | |
| 535 | | `MuseHubContextMusicalState` | Musical state at the target commit | |
| 536 | | `MuseHubContextResponse` | Full context document for a commit | |
| 537 | |
| 538 | ### Objects |
| 539 | |
| 540 | | Name | Description | |
| 541 | |------|-------------| |
| 542 | | `ObjectMetaResponse` | Artifact metadata (no content) | |
| 543 | | `ObjectMetaListResponse` | List of artifact metadata | |
| 544 | |
| 545 | --- |
| 546 | |
| 547 | ## Database ORM Models |
| 548 | |
| 549 | **Path:** `musehub/db/` |
| 550 | |
| 551 | All are SQLAlchemy ORM subclasses of a declarative `Base`. |
| 552 | |
| 553 | ### `db/models.py` — Auth |
| 554 | |
| 555 | | Model | Table | Description | |
| 556 | |-------|-------|-------------| |
| 557 | | `User` | `muse_users` | User account; `id` = JWT `sub` claim | |
| 558 | | `AccessToken` | `muse_access_tokens` | JWT token tracking — stores SHA-256 hash, never the raw token | |
| 559 | |
| 560 | ### `db/muse_cli_models.py` — Muse CLI VCS |
| 561 | |
| 562 | | Model | Table | Description | |
| 563 | |-------|-------|-------------| |
| 564 | | `MuseCliObject` | `muse_objects` | Content-addressed blob | |
| 565 | | `MuseCliSnapshot` | `muse_snapshots` | Immutable snapshot manifest | |
| 566 | | `MuseCliCommit` | `muse_commits` | Versioned commit pointing to a snapshot | |
| 567 | | `MuseCliTag` | `muse_tags` | Music-semantic tag on a CLI commit | |
| 568 | |
| 569 | ### `db/musehub_models.py` — Muse Hub Core |
| 570 | |
| 571 | | Model | Table | Description | |
| 572 | |-------|-------|-------------| |
| 573 | | `MusehubRepo` | `musehub_repos` | Remote repository with music-semantic metadata | |
| 574 | | `MusehubBranch` | `musehub_branches` | Named branch pointer | |
| 575 | | `MusehubCommit` | `musehub_commits` | Commit pushed to Muse Hub | |
| 576 | | `MusehubObject` | `musehub_objects` | Content-addressed binary artifact | |
| 577 | | `MusehubMilestone` | `musehub_milestones` | Milestone grouping issues | |
| 578 | | `MusehubIssueMilestone` | `musehub_issue_milestones` | Issue ↔ Milestone join table | |
| 579 | | `MusehubIssue` | `musehub_issues` | Issue opened against a repo | |
| 580 | | `MusehubIssueComment` | `musehub_issue_comments` | Threaded issue comment | |
| 581 | | `MusehubPullRequest` | `musehub_pull_requests` | Pull request | |
| 582 | | `MusehubPRReview` | `musehub_pr_reviews` | Formal PR review submission | |
| 583 | | `MusehubPRComment` | `musehub_pr_comments` | Inline musical diff comment on a PR | |
| 584 | | `MusehubRelease` | `musehub_releases` | Published version release | |
| 585 | | `MusehubReleaseAsset` | `musehub_release_assets` | Downloadable file attachment | |
| 586 | | `MusehubProfile` | `musehub_profiles` | Public user musical portfolio | |
| 587 | | `MusehubWebhook` | `musehub_webhooks` | Registered webhook subscription | |
| 588 | | `MusehubWebhookDelivery` | `musehub_webhook_deliveries` | Single webhook delivery attempt | |
| 589 | | `MusehubStar` | `musehub_stars` | User star on a public repo | |
| 590 | | `MusehubSession` | `musehub_sessions` | Recording session record pushed from the CLI | |
| 591 | | `MusehubComment` | `musehub_comments` | Threaded comment on any repo object | |
| 592 | | `MusehubReaction` | `musehub_reactions` | Emoji reaction on a comment or target | |
| 593 | | `MusehubFollow` | `musehub_follows` | User follows user — social graph | |
| 594 | | `MusehubWatch` | `musehub_watches` | User watches a repo for notifications | |
| 595 | | `MusehubNotification` | `musehub_notifications` | Notification delivered to a user | |
| 596 | | `MusehubFork` | `musehub_forks` | Fork relationship between two repos | |
| 597 | | `MusehubViewEvent` | `musehub_view_events` | Debounced repo view (one row per visitor per day) | |
| 598 | | `MusehubDownloadEvent` | `musehub_download_events` | Artifact export download event | |
| 599 | | `MusehubRenderJob` | `musehub_render_jobs` | Async MP3/piano-roll render job tracking | |
| 600 | | `MusehubEvent` | `musehub_events` | Append-only repo activity event stream | |
| 601 | |
| 602 | ### `db/musehub_collaborator_models.py` |
| 603 | |
| 604 | | Model | Table | Description | |
| 605 | |-------|-------|-------------| |
| 606 | | `MusehubCollaborator` | `musehub_collaborators` | Explicit push/admin access grant for a user on a repo | |
| 607 | |
| 608 | ### `db/musehub_label_models.py` |
| 609 | |
| 610 | | Model | Table | Description | |
| 611 | |-------|-------|-------------| |
| 612 | | `MusehubLabel` | `musehub_labels` | Coloured label tag scoped to a repo | |
| 613 | | `MusehubIssueLabel` | `musehub_issue_labels` | Issue ↔ Label join table | |
| 614 | | `MusehubPRLabel` | `musehub_pr_labels` | PR ↔ Label join table | |
| 615 | |
| 616 | ### `db/musehub_stash_models.py` |
| 617 | |
| 618 | | Model | Table | Description | |
| 619 | |-------|-------|-------------| |
| 620 | | `MusehubStash` | `musehub_stash` | Named save point for uncommitted changes | |
| 621 | | `MusehubStashEntry` | `musehub_stash_entries` | Single MIDI file snapshot within a stash | |
| 622 | |
| 623 | --- |
| 624 | |
| 625 | ## Entity Hierarchy |
| 626 | |
| 627 | ``` |
| 628 | Muse Hub |
| 629 | │ |
| 630 | ├── MIDI Type Aliases (contracts/midi_types.py) |
| 631 | │ ├── MidiPitch, MidiVelocity, MidiChannel, MidiCC, MidiCCValue |
| 632 | │ ├── MidiAftertouchValue, MidiGMProgram, MidiPitchBend, MidiBPM |
| 633 | │ └── BeatPosition, BeatDuration, ArrangementBeat, ArrangementDuration, Bars |
| 634 | │ |
| 635 | ├── JSON Types (contracts/json_types.py) |
| 636 | │ ├── Primitives: JSONScalar, JSONValue, JSONObject |
| 637 | │ ├── Region maps: RegionNotesMap, RegionCCMap, RegionPitchBendMap, RegionAftertouchMap |
| 638 | │ ├── Protocol aliases: EventJsonSchema, EventSchemaMap |
| 639 | │ └── TypedDicts: NoteDict, CCEventDict, PitchBendDict, AftertouchDict, SectionDict |
| 640 | │ |
| 641 | ├── MCP Protocol (contracts/mcp_types.py) |
| 642 | │ ├── MCPRequest — TypedDict (total=False) |
| 643 | │ ├── MCPResponse — MCPSuccessResponse | MCPErrorResponse |
| 644 | │ ├── MCPMethodResponse — 5-way union of concrete response types |
| 645 | │ ├── MCPToolDef — TypedDict (total=False) |
| 646 | │ ├── DAWToolCallMessage — TypedDict |
| 647 | │ ├── DAWToolResponse — TypedDict (total=False) |
| 648 | │ └── MCPToolDefWire — Pydantic (FastAPI serialisation) |
| 649 | │ |
| 650 | ├── Pydantic Base (contracts/pydantic_types.py, models/base.py) |
| 651 | │ ├── PydanticJson — RootModel: recursive JSON, safe for Pydantic fields |
| 652 | │ └── CamelModel — BaseModel: camelCase alias, populate_by_name, extra=ignore |
| 653 | │ |
| 654 | ├── Auth (auth/tokens.py) |
| 655 | │ └── TokenClaims — TypedDict (total=False): decoded JWT payload |
| 656 | │ |
| 657 | ├── Protocol Events (protocol/events.py) |
| 658 | │ ├── MuseEvent — Pydantic base: type + seq + protocol_version |
| 659 | │ ├── MCPMessageEvent — type: "mcp.message"; payload: dict[str, object] |
| 660 | │ └── MCPPingEvent — type: "mcp.ping" (keepalive) |
| 661 | │ |
| 662 | ├── Protocol Introspection (protocol/) |
| 663 | │ ├── EVENT_REGISTRY — dict[str, type[MuseEvent]] (2 entries) |
| 664 | │ ├── compute_protocol_hash() — SHA-256 over events + tools schemas |
| 665 | │ ├── ProtocolInfoResponse — GET /protocol |
| 666 | │ ├── ProtocolEventsResponse — GET /protocol/events.json |
| 667 | │ ├── ProtocolToolsResponse — GET /protocol/tools.json |
| 668 | │ └── ProtocolSchemaResponse — GET /protocol/schema.json |
| 669 | │ |
| 670 | ├── MCP Tools (mcp/) |
| 671 | │ ├── MUSEHUB_TOOLS — 7 read-only browsing tool definitions |
| 672 | │ ├── MUSEHUB_TOOL_NAMES — frozenset of tool name strings |
| 673 | │ ├── MCP_TOOLS — combined list of all registered tools |
| 674 | │ ├── TOOL_CATEGORIES — dict[str, str] tool → category |
| 675 | │ ├── MuseMCPServer — routes musehub_* calls to executor |
| 676 | │ └── ToolCallResult — server routing result (success, is_error, content) |
| 677 | │ |
| 678 | ├── API Models (models/musehub.py) ~98 Pydantic models |
| 679 | │ ├── VCS: PushRequest, PullResponse, … |
| 680 | │ ├── Repos: CreateRepoRequest, RepoResponse, … |
| 681 | │ ├── Issues: IssueCreate, IssueResponse, IssueCommentResponse, … |
| 682 | │ ├── Pull Requests: PRCreate, PRResponse, PRDiffResponse, … |
| 683 | │ ├── Releases: ReleaseCreate, ReleaseResponse, ReleaseAssetResponse, … |
| 684 | │ ├── Profile + Social: ProfileResponse, StarResponse, … |
| 685 | │ ├── Discovery: ExploreResponse, SearchResponse, GlobalSearchResult, … |
| 686 | │ ├── Timeline + DAG: TimelineResponse, DagGraphResponse, … |
| 687 | │ └── Webhooks: WebhookCreate, WebhookDeliveryResponse, … |
| 688 | │ |
| 689 | └── Database ORM (db/) 37 SQLAlchemy models |
| 690 | ├── Auth: User, AccessToken |
| 691 | ├── CLI VCS: MuseCliObject, MuseCliSnapshot, MuseCliCommit, MuseCliTag |
| 692 | ├── Hub Core: MusehubRepo, MusehubBranch, MusehubCommit, MusehubObject, … |
| 693 | ├── Social: MusehubStar, MusehubFollow, MusehubWatch, MusehubNotification, … |
| 694 | ├── Labels: MusehubLabel, MusehubIssueLabel, MusehubPRLabel |
| 695 | └── Stash: MusehubStash, MusehubStashEntry |
| 696 | ``` |
| 697 | |
| 698 | --- |
| 699 | |
| 700 | ## Entity Graphs (Mermaid) |
| 701 | |
| 702 | Arrow conventions: |
| 703 | - `*--` composition (owns, lifecycle-coupled) |
| 704 | - `-->` association (references) |
| 705 | - `..>` dependency (uses / produces) |
| 706 | - `..|>` implements / extends |
| 707 | |
| 708 | --- |
| 709 | |
| 710 | ### Diagram 1 — MIDI Note and Event Wire Types |
| 711 | |
| 712 | The core typed shapes for MIDI data. `NoteDict` is dual-keyed (camelCase wire |
| 713 | + snake_case storage). The region maps alias these into domain containers. |
| 714 | |
| 715 | ```mermaid |
| 716 | classDiagram |
| 717 | class NoteDict { |
| 718 | <<TypedDict total=False>> |
| 719 | pitch : MidiPitch |
| 720 | velocity : MidiVelocity |
| 721 | channel : MidiChannel |
| 722 | startBeat : BeatPosition |
| 723 | durationBeats : BeatDuration |
| 724 | noteId : str |
| 725 | trackId : str |
| 726 | regionId : str |
| 727 | start_beat : BeatPosition |
| 728 | duration_beats : BeatDuration |
| 729 | note_id : str |
| 730 | track_id : str |
| 731 | region_id : str |
| 732 | layer : str |
| 733 | } |
| 734 | class CCEventDict { |
| 735 | <<TypedDict>> |
| 736 | cc : MidiCC |
| 737 | beat : BeatPosition |
| 738 | value : MidiCCValue |
| 739 | } |
| 740 | class PitchBendDict { |
| 741 | <<TypedDict>> |
| 742 | beat : BeatPosition |
| 743 | value : MidiPitchBend |
| 744 | } |
| 745 | class AftertouchDict { |
| 746 | <<TypedDict total=False>> |
| 747 | beat : BeatPosition (Required) |
| 748 | value : MidiAftertouchValue (Required) |
| 749 | pitch : MidiPitch |
| 750 | } |
| 751 | class SectionDict { |
| 752 | <<TypedDict total=False>> |
| 753 | name : str |
| 754 | start_beat : float |
| 755 | length_beats : float |
| 756 | description : str |
| 757 | per_track_description : dict~str, str~ |
| 758 | } |
| 759 | class RegionNotesMap { |
| 760 | <<TypeAlias>> |
| 761 | dict~str, list~NoteDict~~ |
| 762 | } |
| 763 | class RegionCCMap { |
| 764 | <<TypeAlias>> |
| 765 | dict~str, list~CCEventDict~~ |
| 766 | } |
| 767 | class RegionPitchBendMap { |
| 768 | <<TypeAlias>> |
| 769 | dict~str, list~PitchBendDict~~ |
| 770 | } |
| 771 | class RegionAftertouchMap { |
| 772 | <<TypeAlias>> |
| 773 | dict~str, list~AftertouchDict~~ |
| 774 | } |
| 775 | |
| 776 | RegionNotesMap ..> NoteDict : list element |
| 777 | RegionCCMap ..> CCEventDict : list element |
| 778 | RegionPitchBendMap ..> PitchBendDict : list element |
| 779 | RegionAftertouchMap ..> AftertouchDict : list element |
| 780 | ``` |
| 781 | |
| 782 | --- |
| 783 | |
| 784 | ### Diagram 2 — MCP Protocol Event Types |
| 785 | |
| 786 | The two concrete events Muse Hub relays over SSE — both in the MCP path. |
| 787 | `MuseEvent` is the typed base; the event registry maps type strings to |
| 788 | model classes. |
| 789 | |
| 790 | ```mermaid |
| 791 | classDiagram |
| 792 | class MuseEvent { |
| 793 | <<Pydantic CamelModel>> |
| 794 | +type : str |
| 795 | +seq : int = -1 |
| 796 | +protocol_version : str |
| 797 | extra = "forbid" |
| 798 | } |
| 799 | class MCPMessageEvent { |
| 800 | <<Pydantic CamelModel>> |
| 801 | +type : Literal~"mcp.message"~ |
| 802 | +payload : dict~str, object~ |
| 803 | } |
| 804 | class MCPPingEvent { |
| 805 | <<Pydantic CamelModel>> |
| 806 | +type : Literal~"mcp.ping"~ |
| 807 | } |
| 808 | class EVENT_REGISTRY { |
| 809 | <<dict frozen at import>> |
| 810 | "mcp.message" : MCPMessageEvent |
| 811 | "mcp.ping" : MCPPingEvent |
| 812 | } |
| 813 | |
| 814 | MCPMessageEvent --|> MuseEvent |
| 815 | MCPPingEvent --|> MuseEvent |
| 816 | EVENT_REGISTRY ..> MCPMessageEvent : registers |
| 817 | EVENT_REGISTRY ..> MCPPingEvent : registers |
| 818 | ``` |
| 819 | |
| 820 | --- |
| 821 | |
| 822 | ### Diagram 3 — Protocol Introspection |
| 823 | |
| 824 | The hash computation pipeline and the four HTTP response types it drives. |
| 825 | |
| 826 | ```mermaid |
| 827 | classDiagram |
| 828 | class ProtocolInfoResponse { |
| 829 | <<Pydantic BaseModel>> |
| 830 | +protocolVersion : str |
| 831 | +protocolHash : str |
| 832 | +eventTypes : list~str~ |
| 833 | +eventCount : int |
| 834 | } |
| 835 | class ProtocolEventsResponse { |
| 836 | <<Pydantic BaseModel>> |
| 837 | +protocolVersion : str |
| 838 | +events : dict~str, PydanticJson~ |
| 839 | } |
| 840 | class ProtocolToolsResponse { |
| 841 | <<Pydantic BaseModel>> |
| 842 | +protocolVersion : str |
| 843 | +tools : list~MCPToolDefWire~ |
| 844 | +toolCount : int |
| 845 | } |
| 846 | class ProtocolSchemaResponse { |
| 847 | <<Pydantic BaseModel>> |
| 848 | +protocolVersion : str |
| 849 | +protocolHash : str |
| 850 | +events : dict~str, PydanticJson~ |
| 851 | +tools : list~MCPToolDefWire~ |
| 852 | +toolCount : int |
| 853 | +eventCount : int |
| 854 | } |
| 855 | class MCPToolDefWire { |
| 856 | <<Pydantic BaseModel>> |
| 857 | +name : str |
| 858 | +description : str |
| 859 | +inputSchema : MCPInputSchemaWire |
| 860 | } |
| 861 | |
| 862 | ProtocolSchemaResponse ..> MCPToolDefWire : tools list |
| 863 | ProtocolToolsResponse ..> MCPToolDefWire : tools list |
| 864 | ProtocolSchemaResponse ..> ProtocolEventsResponse : events subset |
| 865 | ProtocolSchemaResponse ..> ProtocolToolsResponse : tools subset |
| 866 | ``` |
| 867 | |
| 868 | --- |
| 869 | |
| 870 | ### Diagram 4 — MCP Tool Routing |
| 871 | |
| 872 | How a tool name flows from an incoming request through the server routing layer |
| 873 | to the executor and back as a `ToolCallResult`. |
| 874 | |
| 875 | ```mermaid |
| 876 | classDiagram |
| 877 | class MuseMCPServer { |
| 878 | +call_tool(name: str, params: dict) ToolCallResult |
| 879 | -_execute_musehub_tool(name, params) MusehubToolResult |
| 880 | -_build_result(result: MusehubToolResult) ToolCallResult |
| 881 | } |
| 882 | class ToolCallResult { |
| 883 | <<dataclass>> |
| 884 | +success : bool |
| 885 | +is_error : bool |
| 886 | +bad_request : bool |
| 887 | +content : list~dict~str, str~~ |
| 888 | } |
| 889 | class MusehubToolResult { |
| 890 | <<dataclass frozen>> |
| 891 | +ok : bool |
| 892 | +data : dict~str, JSONValue~ |
| 893 | +error_code : MusehubErrorCode | None |
| 894 | +error_message : str | None |
| 895 | } |
| 896 | class MusehubErrorCode { |
| 897 | <<Literal>> |
| 898 | "not_found" |
| 899 | "invalid_dimension" |
| 900 | "invalid_mode" |
| 901 | "db_unavailable" |
| 902 | } |
| 903 | class MUSEHUB_TOOLS { |
| 904 | <<list of MCPToolDef>> |
| 905 | musehub_browse_repo |
| 906 | musehub_list_branches |
| 907 | musehub_list_commits |
| 908 | musehub_read_file |
| 909 | musehub_get_analysis |
| 910 | musehub_search |
| 911 | musehub_get_context |
| 912 | } |
| 913 | |
| 914 | MuseMCPServer --> ToolCallResult : returns |
| 915 | MuseMCPServer ..> MusehubToolResult : executor produces |
| 916 | MusehubToolResult --> MusehubErrorCode : error_code |
| 917 | MuseMCPServer ..> MUSEHUB_TOOLS : routes via MUSEHUB_TOOL_NAMES |
| 918 | ``` |
| 919 | |
| 920 | --- |
| 921 | |
| 922 | ### Diagram 5 — MCP JSON-RPC Wire Types |
| 923 | |
| 924 | The full JSON-RPC 2.0 type hierarchy used by the MCP HTTP adapter. |
| 925 | |
| 926 | ```mermaid |
| 927 | classDiagram |
| 928 | class MCPRequest { |
| 929 | <<TypedDict total=False>> |
| 930 | +jsonrpc : str |
| 931 | +id : str | int | None |
| 932 | +method : str |
| 933 | +params : dict~str, object~ |
| 934 | } |
| 935 | class MCPSuccessResponse { |
| 936 | <<TypedDict>> |
| 937 | +jsonrpc : str |
| 938 | +id : str | int | None |
| 939 | +result : object |
| 940 | } |
| 941 | class MCPErrorDetail { |
| 942 | <<TypedDict total=False>> |
| 943 | +code : int |
| 944 | +message : str |
| 945 | +data : object |
| 946 | } |
| 947 | class MCPErrorResponse { |
| 948 | <<TypedDict>> |
| 949 | +jsonrpc : str |
| 950 | +id : str | int | None |
| 951 | +error : MCPErrorDetail |
| 952 | } |
| 953 | class MCPToolDef { |
| 954 | <<TypedDict total=False>> |
| 955 | +name : str |
| 956 | +description : str |
| 957 | +inputSchema : MCPInputSchema |
| 958 | +server_side : bool |
| 959 | } |
| 960 | class MCPContentBlock { |
| 961 | <<TypedDict>> |
| 962 | +type : str |
| 963 | +text : str |
| 964 | } |
| 965 | class MCPCallResult { |
| 966 | <<TypedDict total=False>> |
| 967 | +content : list~MCPContentBlock~ |
| 968 | +isError : bool |
| 969 | } |
| 970 | |
| 971 | MCPErrorResponse *-- MCPErrorDetail : error |
| 972 | MCPCallResult *-- MCPContentBlock : content |
| 973 | MCPToolDef ..> MCPContentBlock : tools/call returns |
| 974 | ``` |
| 975 | |
| 976 | --- |
| 977 | |
| 978 | ### Diagram 6 — Auth and JWT |
| 979 | |
| 980 | Token lifecycle from issuance through validation to the decoded `TokenClaims`. |
| 981 | |
| 982 | ```mermaid |
| 983 | classDiagram |
| 984 | class TokenClaims { |
| 985 | <<TypedDict total=False>> |
| 986 | +type : str (Required) |
| 987 | +iat : int (Required) |
| 988 | +exp : int (Required) |
| 989 | +sub : str |
| 990 | +role : str |
| 991 | } |
| 992 | class AccessToken { |
| 993 | <<SQLAlchemy ORM>> |
| 994 | +id : int |
| 995 | +token_hash : str |
| 996 | +user_id : str |
| 997 | +created_at : datetime |
| 998 | +expires_at : datetime |
| 999 | +revoked : bool |
| 1000 | } |
| 1001 | class User { |
| 1002 | <<SQLAlchemy ORM>> |
| 1003 | +id : str (JWT sub) |
| 1004 | +username : str |
| 1005 | +email : str |
| 1006 | +created_at : datetime |
| 1007 | } |
| 1008 | |
| 1009 | AccessToken --> User : user_id FK |
| 1010 | TokenClaims ..> User : sub maps to User.id |
| 1011 | TokenClaims ..> AccessToken : validated against token_hash |
| 1012 | ``` |
| 1013 | |
| 1014 | --- |
| 1015 | |
| 1016 | ### Diagram 7 — Muse Hub Repository Object Graph |
| 1017 | |
| 1018 | The core VCS entity relationships in the database. |
| 1019 | |
| 1020 | ```mermaid |
| 1021 | classDiagram |
| 1022 | class MusehubRepo { |
| 1023 | <<SQLAlchemy ORM>> |
| 1024 | +id : str |
| 1025 | +name : str |
| 1026 | +owner_id : str |
| 1027 | +description : str |
| 1028 | +is_public : bool |
| 1029 | +created_at : datetime |
| 1030 | } |
| 1031 | class MusehubBranch { |
| 1032 | <<SQLAlchemy ORM>> |
| 1033 | +id : str |
| 1034 | +repo_id : str (FK) |
| 1035 | +name : str |
| 1036 | +head_commit_id : str |
| 1037 | } |
| 1038 | class MusehubCommit { |
| 1039 | <<SQLAlchemy ORM>> |
| 1040 | +id : str |
| 1041 | +repo_id : str (FK) |
| 1042 | +branch_id : str (FK) |
| 1043 | +snapshot_id : str |
| 1044 | +message : str |
| 1045 | +committed_at : datetime |
| 1046 | } |
| 1047 | class MusehubObject { |
| 1048 | <<SQLAlchemy ORM>> |
| 1049 | +id : str |
| 1050 | +repo_id : str (FK) |
| 1051 | +commit_id : str (FK) |
| 1052 | +path : str |
| 1053 | +content_hash : str |
| 1054 | +mime_type : str |
| 1055 | } |
| 1056 | class MusehubIssue { |
| 1057 | <<SQLAlchemy ORM>> |
| 1058 | +id : str |
| 1059 | +repo_id : str (FK) |
| 1060 | +title : str |
| 1061 | +state : str |
| 1062 | +created_at : datetime |
| 1063 | } |
| 1064 | class MusehubPullRequest { |
| 1065 | <<SQLAlchemy ORM>> |
| 1066 | +id : str |
| 1067 | +repo_id : str (FK) |
| 1068 | +source_branch : str |
| 1069 | +target_branch : str |
| 1070 | +state : str |
| 1071 | } |
| 1072 | class MusehubRelease { |
| 1073 | <<SQLAlchemy ORM>> |
| 1074 | +id : str |
| 1075 | +repo_id : str (FK) |
| 1076 | +tag : str |
| 1077 | +title : str |
| 1078 | +created_at : datetime |
| 1079 | } |
| 1080 | |
| 1081 | MusehubRepo *-- MusehubBranch : branches |
| 1082 | MusehubRepo *-- MusehubCommit : commits |
| 1083 | MusehubRepo *-- MusehubObject : objects |
| 1084 | MusehubRepo *-- MusehubIssue : issues |
| 1085 | MusehubRepo *-- MusehubPullRequest : pull requests |
| 1086 | MusehubRepo *-- MusehubRelease : releases |
| 1087 | MusehubBranch --> MusehubCommit : head_commit_id |
| 1088 | MusehubCommit --> MusehubObject : objects |
| 1089 | ``` |
| 1090 | |
| 1091 | --- |
| 1092 | |
| 1093 | ### Diagram 8 — Social and Discovery Graph |
| 1094 | |
| 1095 | User-to-user and user-to-repo relationships powering the social feed and |
| 1096 | discovery pages. |
| 1097 | |
| 1098 | ```mermaid |
| 1099 | classDiagram |
| 1100 | class User { |
| 1101 | <<SQLAlchemy ORM>> |
| 1102 | +id : str |
| 1103 | +username : str |
| 1104 | } |
| 1105 | class MusehubProfile { |
| 1106 | <<SQLAlchemy ORM>> |
| 1107 | +user_id : str (FK) |
| 1108 | +display_name : str |
| 1109 | +bio : str |
| 1110 | } |
| 1111 | class MusehubStar { |
| 1112 | <<SQLAlchemy ORM>> |
| 1113 | +user_id : str (FK) |
| 1114 | +repo_id : str (FK) |
| 1115 | +starred_at : datetime |
| 1116 | } |
| 1117 | class MusehubFollow { |
| 1118 | <<SQLAlchemy ORM>> |
| 1119 | +follower_id : str (FK) |
| 1120 | +followee_id : str (FK) |
| 1121 | +created_at : datetime |
| 1122 | } |
| 1123 | class MusehubWatch { |
| 1124 | <<SQLAlchemy ORM>> |
| 1125 | +user_id : str (FK) |
| 1126 | +repo_id : str (FK) |
| 1127 | } |
| 1128 | class MusehubNotification { |
| 1129 | <<SQLAlchemy ORM>> |
| 1130 | +id : str |
| 1131 | +user_id : str (FK) |
| 1132 | +event_type : str |
| 1133 | +read : bool |
| 1134 | } |
| 1135 | class MusehubRepo { |
| 1136 | <<SQLAlchemy ORM>> |
| 1137 | +id : str |
| 1138 | +name : str |
| 1139 | } |
| 1140 | |
| 1141 | User *-- MusehubProfile : profile |
| 1142 | User ..> MusehubStar : stars |
| 1143 | User ..> MusehubFollow : follows / followed_by |
| 1144 | User ..> MusehubWatch : watches |
| 1145 | User ..> MusehubNotification : receives |
| 1146 | MusehubStar --> MusehubRepo : repo_id |
| 1147 | MusehubWatch --> MusehubRepo : repo_id |
| 1148 | ``` |
| 1149 | |
| 1150 | --- |
| 1151 | |
| 1152 | ### Diagram 9 — Full Entity Overview |
| 1153 | |
| 1154 | All named layers and the dependency flow between them. |
| 1155 | |
| 1156 | ```mermaid |
| 1157 | classDiagram |
| 1158 | class MIDIAliases { |
| 1159 | <<contracts/midi_types.py>> |
| 1160 | MidiPitch · MidiVelocity · BeatPosition · BeatDuration · … |
| 1161 | } |
| 1162 | class JSONTypes { |
| 1163 | <<contracts/json_types.py>> |
| 1164 | NoteDict · CCEventDict · PitchBendDict · AftertouchDict · SectionDict |
| 1165 | JSONValue · JSONObject · RegionNotesMap · EventJsonSchema |
| 1166 | } |
| 1167 | class MCPTypes { |
| 1168 | <<contracts/mcp_types.py>> |
| 1169 | MCPRequest · MCPResponse · MCPToolDef · DAWToolCallMessage |
| 1170 | MCPToolDefWire (Pydantic) |
| 1171 | } |
| 1172 | class PydanticBase { |
| 1173 | <<contracts/pydantic_types.py + models/base.py>> |
| 1174 | PydanticJson (RootModel) |
| 1175 | CamelModel (BaseModel) |
| 1176 | } |
| 1177 | class AuthTypes { |
| 1178 | <<auth/tokens.py>> |
| 1179 | TokenClaims (TypedDict) |
| 1180 | } |
| 1181 | class ProtocolEvents { |
| 1182 | <<protocol/events.py>> |
| 1183 | MuseEvent (base) |
| 1184 | MCPMessageEvent · MCPPingEvent |
| 1185 | } |
| 1186 | class ProtocolResponses { |
| 1187 | <<protocol/responses.py>> |
| 1188 | ProtocolInfoResponse |
| 1189 | ProtocolEventsResponse |
| 1190 | ProtocolToolsResponse |
| 1191 | ProtocolSchemaResponse |
| 1192 | } |
| 1193 | class MCPTools { |
| 1194 | <<mcp/>> |
| 1195 | MUSEHUB_TOOLS (7 tools) |
| 1196 | MuseMCPServer · ToolCallResult |
| 1197 | MusehubToolResult |
| 1198 | } |
| 1199 | class APIModels { |
| 1200 | <<models/musehub.py>> |
| 1201 | ~98 Pydantic CamelModel subclasses |
| 1202 | VCS · Repos · Issues · PRs · Releases · Social · Search |
| 1203 | } |
| 1204 | class DatabaseORM { |
| 1205 | <<db/>> |
| 1206 | 37 SQLAlchemy models |
| 1207 | User · AccessToken · MusehubRepo · MusehubBranch · MusehubCommit · … |
| 1208 | } |
| 1209 | |
| 1210 | JSONTypes ..> MIDIAliases : constrained int/float aliases |
| 1211 | MCPTypes ..> PydanticBase : MCPToolDefWire extends CamelModel |
| 1212 | ProtocolEvents ..> PydanticBase : MuseEvent extends CamelModel |
| 1213 | ProtocolResponses ..> PydanticBase : all extend BaseModel |
| 1214 | ProtocolResponses ..> MCPTypes : MCPToolDefWire in tools fields |
| 1215 | MCPTools ..> MCPTypes : MUSEHUB_TOOLS match MCPToolDef shape |
| 1216 | MCPTools ..> JSONTypes : MusehubToolResult.data uses JSONValue |
| 1217 | APIModels ..> PydanticBase : all extend CamelModel |
| 1218 | AuthTypes ..> DatabaseORM : TokenClaims.sub → User.id |
| 1219 | ``` |