cgcardona / muse public
type-contracts.md markdown
2283 lines 86.2 KB
dfaf1b77 refactor: rename muse-work/ → state/ Gabriel Cardona <gabriel@tellurstori.com> 8h ago
1 # Muse VCS — Type Contracts Reference
2
3 > Updated: 2026-03-18 (v0.1.2) | Reflects every named entity in the Muse VCS surface.
4 > `Any` and `object` do not exist in any production file. Every type boundary
5 > is named. The typing audit ratchet enforces zero violations on every CI run.
6
7 This document is the single source of truth for every named entity —
8 `TypedDict`, `dataclass`, `Protocol`, `Enum`, `TypeAlias` — in the Muse
9 codebase. It covers the full contract of each type: fields, types,
10 optionality, and intended use.
11
12 ---
13
14 ## Table of Contents
15
16 1. [Design Philosophy](#design-philosophy)
17 2. [Domain Protocol Types (`muse/domain.py`)](#domain-protocol-types)
18 - [Type Aliases](#type-aliases)
19 - [Operation TypedDicts — DomainOp variants](#operation-typeddicts)
20 - [Snapshot and Delta TypedDicts](#snapshot-and-delta-typeddicts)
21 - [Result Dataclasses](#result-dataclasses)
22 - [MuseDomainPlugin Protocol](#musedomainplugin-protocol)
23 - [Optional Protocol Extensions](#optional-protocol-extensions)
24 3. [Domain Schema Types (`muse/core/schema.py`)](#domain-schema-types)
25 4. [Diff Algorithm Types (`muse/core/diff_algorithms/`)](#diff-algorithm-types)
26 5. [OT Merge Types (`muse/core/op_transform.py`)](#ot-merge-types)
27 6. [Op Log Types (`muse/core/op_log.py`)](#op-log-types)
28 7. [CRDT Primitive Types (`muse/core/crdts/`)](#crdt-primitive-types)
29 8. [Store Types (`muse/core/store.py`)](#store-types)
30 9. [Merge Engine Types (`muse/core/merge_engine.py`)](#merge-engine-types)
31 10. [Attributes Types (`muse/core/attributes.py`)](#attributes-types)
32 11. [MIDI Dimension Merge Types (`muse/plugins/midi/midi_merge.py`)](#midi-dimension-merge-types)
33 12. [Code Plugin Types (`muse/plugins/code/`)](#code-plugin-types)
34 13. [Configuration Types (`muse/cli/config.py`)](#configuration-types)
35 14. [MIDI / MusicXML Import Types (`muse/cli/midi_parser.py`)](#midi--musicxml-import-types)
36 15. [Stash Types (`muse/cli/commands/stash.py`)](#stash-types)
37 16. [Error Hierarchy (`muse/core/errors.py`)](#error-hierarchy)
38 17. [Entity Hierarchy](#entity-hierarchy)
39 18. [Entity Graphs (Mermaid)](#entity-graphs-mermaid)
40
41 ---
42
43 ## Design Philosophy
44
45 Every entity in this codebase follows five rules:
46
47 1. **No `Any`. No `object`. Ever.** Both collapse type safety for downstream
48 callers. Every boundary is typed with a concrete named entity — `TypedDict`,
49 `dataclass`, `Protocol`, or a specific union. The CI typing audit enforces
50 this with a ratchet of zero violations.
51
52 2. **No covariance in collection aliases.** `dict[str, str]` and
53 `list[str]` are used directly. If a function's return mixes value types,
54 create a `TypedDict` for that shape instead of using `dict[str, str | int]`.
55
56 3. **Boundaries own coercion.** When external data arrives (JSON from disk,
57 TOML from config, MIDI bytes from disk), the boundary module coerces it
58 to the canonical internal type using `isinstance` narrowing. Downstream
59 code always sees clean types.
60
61 4. **Wire-format TypedDicts for serialisation, dataclasses for in-memory
62 logic.** `CommitDict`, `SnapshotDict`, `TagDict` are JSON-serialisable
63 and used by `to_dict()` / `from_dict()`. `CommitRecord`, `SnapshotRecord`,
64 `TagRecord` are rich dataclasses with typed `datetime` fields used in
65 business logic.
66
67 5. **The plugin protocol is the extension point.** All domain-specific logic
68 lives behind `MuseDomainPlugin`. The core DAG engine, branching, and
69 merge machinery know nothing about music, genomics, or any other domain.
70 Swapping domains is a one-file operation.
71
72 ### Banned → replacement table
73
74 | Banned | Use instead |
75 |--------|-------------|
76 | `Any` | `TypedDict`, `dataclass`, specific union |
77 | `object` | The actual type or a constrained union |
78 | `list` (bare) | `list[X]` with concrete element type |
79 | `dict` (bare) | `dict[K, V]` with concrete key/value types |
80 | `dict[str, X]` with known keys | `TypedDict` — name the keys |
81 | `Optional[X]` | `X \| None` |
82 | Legacy `List`, `Dict`, `Set`, `Tuple` | Lowercase builtins |
83 | `cast(T, x)` | Fix the callee to return `T` |
84 | `# type: ignore` | Fix the underlying type error |
85
86 ---
87
88 ## Domain Protocol Types
89
90 **Path:** `muse/domain.py`
91
92 The six-interface contract that every domain plugin must satisfy. The core
93 engine implements the DAG, branching, merge-base finding, and lineage walking.
94 A domain plugin provides the six methods and gets the full VCS for free.
95 Two optional protocol extensions (`StructuredMergePlugin`, `CRDTPlugin`) unlock
96 richer merge semantics.
97
98 ### Type Aliases
99
100 | Alias | Definition | Description |
101 |-------|-----------|-------------|
102 | `DomainAddress` | `str` | Stable human-readable address of an element within the domain namespace (e.g. `"note:4:60"` in MIDI, `"src/mod.py#Foo.bar"` in code) |
103 | `SemVerBump` | `Literal["major", "minor", "patch", "none"]` | Semantic version impact of a delta |
104 | `LeafDomainOp` | `InsertOp \| DeleteOp \| MoveOp \| ReplaceOp \| MutateOp` | All non-recursive operation variants |
105 | `DomainOp` | `LeafDomainOp \| PatchOp` | The complete discriminated union; `PatchOp.child_ops` is `list[DomainOp]` (recursive) |
106 | `LiveState` | `SnapshotManifest \| pathlib.Path` | Current domain state — either an in-memory snapshot or the `state/` directory path |
107 | `StateSnapshot` | `SnapshotManifest` | A content-addressed, immutable capture of state at a point in time |
108 | `StateDelta` | `StructuredDelta` | The typed delta between two snapshots |
109
110 ### Operation TypedDicts
111
112 Every `DomainOp` variant is a `TypedDict` with an `op` discriminator and an
113 `address` identifying the element within the domain's namespace.
114
115 #### `InsertOp`
116
117 `TypedDict` — Add a new element.
118
119 | Field | Type | Description |
120 |-------|------|-------------|
121 | `op` | `Literal["insert"]` | Discriminator |
122 | `address` | `DomainAddress` | Stable address of the new element |
123 | `position` | `int \| None` | Insertion position; `None` for unordered collections |
124 | `content_id` | `str` | SHA-256 of the inserted element's serialised content |
125 | `content_summary` | `str` | Human-readable one-liner for display |
126
127 #### `DeleteOp`
128
129 `TypedDict` — Remove an existing element.
130
131 | Field | Type | Description |
132 |-------|------|-------------|
133 | `op` | `Literal["delete"]` | Discriminator |
134 | `address` | `DomainAddress` | Address of the element being removed |
135 | `position` | `int \| None` | Position at time of deletion; `None` for unordered collections |
136 | `content_id` | `str` | SHA-256 of the removed element (for reverse-application) |
137 | `content_summary` | `str` | Human-readable one-liner for display |
138
139 #### `MoveOp`
140
141 `TypedDict` — Reposition an element within a sequence.
142
143 | Field | Type | Description |
144 |-------|------|-------------|
145 | `op` | `Literal["move"]` | Discriminator |
146 | `address` | `DomainAddress` | Address of the element being moved |
147 | `from_position` | `int` | Original position |
148 | `to_position` | `int` | Target position |
149 | `content_id` | `str` | SHA-256 of the element content (unchanged by the move) |
150
151 #### `ReplaceOp`
152
153 `TypedDict` — Atomic swap of one content version for another.
154
155 | Field | Type | Description |
156 |-------|------|-------------|
157 | `op` | `Literal["replace"]` | Discriminator |
158 | `address` | `DomainAddress` | Address of the replaced element |
159 | `position` | `int \| None` | Position within its container; `None` for unordered |
160 | `old_content_id` | `str` | SHA-256 of the previous content |
161 | `new_content_id` | `str` | SHA-256 of the new content |
162 | `old_summary` | `str` | Human-readable description of the before state |
163 | `new_summary` | `str` | Human-readable description of the after state |
164
165 #### `FieldMutation`
166
167 `TypedDict` — A single field change within a `MutateOp`. Used as the value
168 type of `MutateOp.fields`.
169
170 | Field | Type | Description |
171 |-------|------|-------------|
172 | `old` | `str` | Serialised previous field value |
173 | `new` | `str` | Serialised new field value |
174
175 #### `MutateOp`
176
177 `TypedDict` — Partial field-level update of a structured entity. More precise
178 than `ReplaceOp` when only certain fields change (e.g. velocity on a note,
179 author on a code symbol).
180
181 | Field | Type | Description |
182 |-------|------|-------------|
183 | `op` | `Literal["mutate"]` | Discriminator |
184 | `address` | `DomainAddress` | Address of the mutated entity |
185 | `entity_id` | `str` | Stable domain-assigned entity identifier |
186 | `old_content_id` | `str` | SHA-256 of the entity before mutation |
187 | `new_content_id` | `str` | SHA-256 of the entity after mutation |
188 | `fields` | `dict[str, FieldMutation]` | Field name → before/after values for each changed field |
189 | `old_summary` | `str` | Human-readable before state |
190 | `new_summary` | `str` | Human-readable after state |
191 | `position` | `int \| None` | Position within its container; `None` for unordered |
192
193 #### `PatchOp`
194
195 `TypedDict` — Container op for hierarchical or multi-file changes. Carries a
196 list of child `DomainOp`s belonging to a sub-domain or nested structure.
197 `child_ops` is `list[DomainOp]` — the type is recursive.
198
199 | Field | Type | Description |
200 |-------|------|-------------|
201 | `op` | `Literal["patch"]` | Discriminator |
202 | `address` | `DomainAddress` | Address of the patched container element |
203 | `child_ops` | `list[DomainOp]` | Nested operations in the child domain |
204 | `child_domain` | `str` | Identifier of the sub-domain (e.g. `"midi_track"`) |
205 | `child_summary` | `str` | Human-readable summary of the child ops |
206
207 ### Snapshot and Delta TypedDicts
208
209 #### `SnapshotManifest`
210
211 `TypedDict` — Content-addressed snapshot of domain state. JSON-serialisable
212 and content-addressable via SHA-256. Aliased as `StateSnapshot`.
213
214 | Field | Type | Description |
215 |-------|------|-------------|
216 | `files` | `dict[str, str]` | Workspace-relative POSIX paths → SHA-256 content digests |
217 | `domain` | `str` | Plugin identifier that produced this snapshot (e.g. `"midi"`) |
218
219 #### `StructuredDelta`
220
221 `TypedDict (total=False)` — The typed delta produced by `MuseDomainPlugin.diff()`.
222 Aliased as `StateDelta`. All fields are optional to support format-version
223 evolution: older records have fewer fields.
224
225 | Field | Type | Description |
226 |-------|------|-------------|
227 | `domain` | `str` | Plugin identifier that produced this delta |
228 | `ops` | `list[DomainOp]` | Ordered list of typed domain operations |
229 | `summary` | `str` | Human-readable summary (e.g. `"3 inserts, 1 delete"`) |
230 | `sem_ver_bump` | `SemVerBump` | Semantic version impact (`"major"`, `"minor"`, `"patch"`, `"none"`) |
231 | `breaking_changes` | `list[str]` | Addresses of public symbols that were removed or incompatibly changed |
232
233 #### `CRDTSnapshotManifest`
234
235 `TypedDict` — Extended snapshot format for CRDT-mode plugins. Wraps the plain
236 snapshot manifest with a vector clock and serialised CRDT state per dimension.
237
238 | Field | Type | Description |
239 |-------|------|-------------|
240 | `schema_version` | `Literal[1]` | Always `1` |
241 | `domain` | `str` | Plugin domain name |
242 | `files` | `dict[str, str]` | POSIX path → SHA-256 object digest (same as `SnapshotManifest`) |
243 | `vclock` | `dict[str, int]` | Vector clock: agent ID → logical clock value |
244 | `crdt_state` | `dict[str, str]` | Dimension name → hash of serialised CRDT primitive state in object store |
245
246 #### `EntityProvenance`
247
248 `TypedDict (total=False)` — Optional causal provenance attached to a domain entity.
249 All fields are optional; older records have none.
250
251 | Field | Type | Description |
252 |-------|------|-------------|
253 | `entity_id` | `str` | Stable domain-assigned identifier for this entity |
254 | `origin_op_id` | `str` | `OpEntry.op_id` of the op that created this entity |
255 | `last_modified_op_id` | `str` | `OpEntry.op_id` of the most recent mutation |
256 | `created_at_commit` | `str` | Commit ID of the commit that first introduced this entity |
257 | `actor_id` | `str` | Agent or human identity of the creator |
258
259 ### Result Dataclasses
260
261 #### `ConflictRecord`
262
263 `@dataclass` — Structured description of a single merge conflict. More
264 informative than a bare path string; carries the conflict type and per-branch
265 summaries.
266
267 | Field | Type | Default | Description |
268 |-------|------|---------|-------------|
269 | `path` | `str` | required | Workspace-relative path of the conflicting file |
270 | `conflict_type` | `str` | `"file_level"` | One of: `"file_level"`, `"symbol_edit_overlap"`, `"rename_edit"`, … |
271 | `ours_summary` | `str` | `""` | Human-readable description of the change on our branch |
272 | `theirs_summary` | `str` | `""` | Human-readable description of the change on their branch |
273 | `addresses` | `list[str]` | `[]` | `DomainAddress` values of all conflicting elements |
274
275 **Methods:** `to_dict() -> dict[str, str | list[str]]`
276
277 #### `MergeResult`
278
279 `@dataclass` — Outcome of a three-way merge between two divergent state lines.
280 An empty `conflicts` list means the merge was clean.
281
282 | Field | Type | Default | Description |
283 |-------|------|---------|-------------|
284 | `merged` | `StateSnapshot` | required | The reconciled snapshot |
285 | `conflicts` | `list[str]` | `[]` | Workspace-relative paths that could not be auto-merged |
286 | `applied_strategies` | `dict[str, str]` | `{}` | Path → strategy applied by `.museattributes` (e.g. `{"drums/kick.mid": "ours"}`) |
287 | `dimension_reports` | `dict[str, dict[str, str]]` | `{}` | Path → per-dimension winner map; only populated for MIDI files that went through dimension-level merge |
288 | `op_log` | `list[DomainOp]` | `[]` | Ordered list of ops that were auto-merged from both branches |
289 | `conflict_records` | `list[ConflictRecord]` | `[]` | Structured conflict descriptions; parallel to `conflicts` paths |
290
291 **Property:**
292
293 | Name | Returns | Description |
294 |------|---------|-------------|
295 | `is_clean` | `bool` | `True` when `conflicts` is empty |
296
297 #### `DriftReport`
298
299 `@dataclass` — Gap between committed state and current live state. Produced by
300 `MuseDomainPlugin.drift()` and consumed by `muse status`.
301
302 | Field | Type | Default | Description |
303 |-------|------|---------|-------------|
304 | `has_drift` | `bool` | required | `True` when live state differs from committed snapshot |
305 | `summary` | `str` | `""` | Human-readable description (e.g. `"2 added, 1 modified"`) |
306 | `delta` | `StateDelta` | empty `StructuredDelta` | Machine-readable diff for programmatic consumers |
307
308 ### MuseDomainPlugin Protocol
309
310 `@runtime_checkable Protocol` — The six interfaces a domain plugin must
311 implement. Runtime-checkable so that `assert isinstance(plugin, MuseDomainPlugin)`
312 works as a module-load sanity check.
313
314 | Method | Signature | Description |
315 |--------|-----------|-------------|
316 | `snapshot` | `(live_state: LiveState) -> StateSnapshot` | Capture current state as a content-addressed dict; must honour `.museignore` (TOML — call `load_ignore_config` + `resolve_patterns(config, domain)` from `muse.core.ignore`) |
317 | `diff` | `(base: StateSnapshot, target: StateSnapshot, *, repo_root: pathlib.Path \| None = None) -> StateDelta` | Compute the typed delta between two snapshots |
318 | `merge` | `(base, left, right: StateSnapshot, *, repo_root: pathlib.Path \| None = None) -> MergeResult` | Three-way merge; when `repo_root` is provided, load `.museattributes` and perform dimension-level merge for supported formats |
319 | `drift` | `(committed: StateSnapshot, live: LiveState) -> DriftReport` | Compare committed state vs current live state |
320 | `apply` | `(delta: StateDelta, live_state: LiveState) -> LiveState` | Apply a delta to produce a new live state |
321 | `schema` | `() -> DomainSchema` | Declare the structural shape of the domain's data (drives diff algorithm selection) |
322
323 The MIDI plugin (`muse.plugins.midi.plugin`) is the reference implementation.
324 Every other domain implements these six methods and registers itself as a plugin.
325
326 ### Optional Protocol Extensions
327
328 #### `StructuredMergePlugin`
329
330 `@runtime_checkable Protocol` — Extends `MuseDomainPlugin` with operation-level
331 OT merge. When both branches produce `StructuredDelta`s, the merge engine detects
332 `isinstance(plugin, StructuredMergePlugin)` and calls `merge_ops()` instead of
333 `merge()`.
334
335 | Method | Signature | Description |
336 |--------|-----------|-------------|
337 | `merge_ops` | `(base, ours_snap, theirs_snap, ours_ops, theirs_ops, *, repo_root) -> MergeResult` | Operation-level three-way merge using OT commutativity rules |
338
339 #### `CRDTPlugin`
340
341 `@runtime_checkable Protocol` — Extends `MuseDomainPlugin` with convergent merge.
342 `join` always succeeds — no conflict state ever exists.
343
344 | Method | Signature | Description |
345 |--------|-----------|-------------|
346 | `crdt_schema` | `() -> list[CRDTDimensionSpec]` | Per-dimension CRDT primitive specification |
347 | `join` | `(a: CRDTSnapshotManifest, b: CRDTSnapshotManifest) -> CRDTSnapshotManifest` | Convergent join satisfying commutativity, associativity, idempotency |
348 | `to_crdt_state` | `(snapshot: StateSnapshot) -> CRDTSnapshotManifest` | Convert a snapshot into CRDT state |
349 | `from_crdt_state` | `(crdt: CRDTSnapshotManifest) -> StateSnapshot` | Convert CRDT state back to a plain snapshot |
350
351 ---
352
353 ## Domain Schema Types
354
355 **Path:** `muse/core/schema.py`
356
357 The `DomainSchema` family of TypedDicts allows a plugin to declare its data
358 structure. The core engine uses this to select diff algorithms per-dimension
359 and to drive informed conflict reporting during OT merge.
360
361 ### Element Schema TypedDicts
362
363 #### `SequenceSchema`
364
365 `TypedDict` — Ordered sequence of homogeneous elements (LCS-diffable).
366
367 | Field | Type | Description |
368 |-------|------|-------------|
369 | `kind` | `Literal["sequence"]` | Discriminator |
370 | `element_type` | `str` | Domain name for elements (e.g. `"note"`, `"nucleotide"`) |
371 | `identity` | `Literal["by_id", "by_position", "by_content"]` | How two elements are considered the same |
372 | `diff_algorithm` | `Literal["lcs", "myers", "patience"]` | LCS variant to use |
373 | `alphabet` | `list[str] \| None` | Optional closed set of valid values for validation |
374
375 #### `TreeSchema`
376
377 `TypedDict` — Hierarchical labeled ordered tree (tree-edit-diffable).
378
379 | Field | Type | Description |
380 |-------|------|-------------|
381 | `kind` | `Literal["tree"]` | Discriminator |
382 | `node_type` | `str` | Domain name for tree nodes (e.g. `"ast_node"`, `"scene_node"`) |
383 | `diff_algorithm` | `Literal["zhang_shasha", "gumtree"]` | Tree edit algorithm |
384
385 #### `TensorSchema`
386
387 `TypedDict` — N-dimensional numerical array (numerical-diffable).
388
389 | Field | Type | Description |
390 |-------|------|-------------|
391 | `kind` | `Literal["tensor"]` | Discriminator |
392 | `dtype` | `Literal["float32", "float64", "int8", "int16", "int32", "int64"]` | Element data type |
393 | `rank` | `int` | Number of dimensions |
394 | `epsilon` | `float` | Floating-point drift below this threshold is not a change |
395 | `diff_mode` | `Literal["sparse", "block", "full"]` | Output granularity: element-level, range-grouped, or whole-array |
396
397 #### `SetSchema`
398
399 `TypedDict` — Unordered collection of elements (set-algebra-diffable).
400
401 | Field | Type | Description |
402 |-------|------|-------------|
403 | `kind` | `Literal["set"]` | Discriminator |
404 | `element_type` | `str` | Domain name for elements |
405 | `identity` | `Literal["by_content", "by_id"]` | How two elements are considered the same |
406
407 #### `MapSchema`
408
409 `TypedDict` — Key-value map whose values follow a sub-schema.
410
411 | Field | Type | Description |
412 |-------|------|-------------|
413 | `kind` | `Literal["map"]` | Discriminator |
414 | `key_type` | `str` | Domain name for keys |
415 | `value_schema` | `ElementSchema` | Schema applied to each value (forward reference — resolved lazily) |
416 | `identity` | `Literal["by_key"]` | Elements are always identified by key |
417
418 #### `ElementSchema` (Type Alias)
419
420 `TypeAlias = SequenceSchema | TreeSchema | TensorSchema | MapSchema | SetSchema`
421
422 The discriminated union of all element-level schema variants. The `kind` field
423 is the discriminator.
424
425 ### Dimension Schema TypedDicts
426
427 #### `DimensionSpec`
428
429 `TypedDict` — Schema for one orthogonal dimension within a domain.
430
431 | Field | Type | Description |
432 |-------|------|-------------|
433 | `name` | `str` | Dimension name (e.g. `"notes"`, `"pitch_bend"`) |
434 | `description` | `str` | Human-readable description |
435 | `schema` | `ElementSchema` | Element schema governing diff algorithm selection for this dimension |
436 | `independent_merge` | `bool` | When `True`, this dimension can be auto-merged even when another dimension conflicts |
437
438 #### `CRDTPrimitive` (Type Alias)
439
440 `TypeAlias = Literal["lww_register", "or_set", "rga", "aw_map", "g_counter"]`
441
442 The set of valid CRDT primitive names for use in `CRDTDimensionSpec.crdt_type`.
443
444 #### `CRDTDimensionSpec`
445
446 `TypedDict` — Schema for a dimension using CRDT convergent-merge semantics.
447
448 | Field | Type | Description |
449 |-------|------|-------------|
450 | `name` | `str` | Dimension name |
451 | `description` | `str` | Human-readable description |
452 | `crdt_type` | `CRDTPrimitive` | CRDT primitive governing this dimension |
453 | `independent_merge` | `bool` | Whether this dimension merges independently |
454
455 ### Top-Level Schema
456
457 #### `DomainSchema`
458
459 `TypedDict` — Top-level schema declaration returned by `MuseDomainPlugin.schema()`.
460
461 | Field | Type | Description |
462 |-------|------|-------------|
463 | `domain` | `str` | Plugin domain name |
464 | `description` | `str` | Human-readable domain description |
465 | `dimensions` | `list[DimensionSpec]` | Orthogonal dimension declarations |
466 | `top_level` | `ElementSchema` | Schema for the domain's root container |
467 | `merge_mode` | `Literal["three_way", "crdt"]` | `"three_way"` uses OT merge; `"crdt"` uses convergent join |
468 | `schema_version` | `Literal[1]` | Always `1` |
469
470 ---
471
472 ## Diff Algorithm Types
473
474 **Path:** `muse/core/diff_algorithms/`
475
476 ### Input TypedDicts
477
478 Each `ElementSchema` variant has a paired input TypedDict consumed by
479 `diff_by_schema()`. The `kind` discriminator matches.
480
481 #### `SequenceInput`
482
483 | Field | Type | Description |
484 |-------|------|-------------|
485 | `kind` | `Literal["sequence"]` | Discriminator |
486 | `items` | `list[str]` | Ordered content IDs (SHA-256 digests) |
487
488 #### `SetInput`
489
490 | Field | Type | Description |
491 |-------|------|-------------|
492 | `kind` | `Literal["set"]` | Discriminator |
493 | `items` | `frozenset[str]` | Set of content IDs |
494
495 #### `TensorInput`
496
497 | Field | Type | Description |
498 |-------|------|-------------|
499 | `kind` | `Literal["tensor"]` | Discriminator |
500 | `values` | `list[float]` | Flat array of numerical values |
501
502 #### `MapInput`
503
504 | Field | Type | Description |
505 |-------|------|-------------|
506 | `kind` | `Literal["map"]` | Discriminator |
507 | `entries` | `dict[str, str]` | Key → content ID |
508
509 #### `TreeInput`
510
511 | Field | Type | Description |
512 |-------|------|-------------|
513 | `kind` | `Literal["tree"]` | Discriminator |
514 | `root` | `TreeNode` | Root of the labeled ordered tree |
515
516 #### `DiffInput` (Type Alias)
517
518 `TypeAlias = SequenceInput | SetInput | TensorInput | MapInput | TreeInput`
519
520 ### LCS Types (`muse/core/diff_algorithms/lcs.py`)
521
522 #### `EditKind` (Type Alias)
523
524 `TypeAlias = Literal["keep", "insert", "delete"]`
525
526 #### `EditStep`
527
528 `@dataclass (frozen=True)` — One step in the edit script produced by
529 `myers_ses()`. Applying all steps in order to `base` reproduces `target`.
530
531 | Field | Type | Description |
532 |-------|------|-------------|
533 | `kind` | `EditKind` | `"keep"` (copy from base), `"insert"` (add from target), `"delete"` (skip from base) |
534 | `base_index` | `int` | Index in the base content-ID list; `-1` for pure inserts |
535 | `target_index` | `int` | Index in the target content-ID list; `-1` for pure deletes |
536 | `item` | `str` | Content ID of the element |
537
538 ### Tree Types (`muse/core/diff_algorithms/tree_edit.py`)
539
540 #### `TreeNode`
541
542 `@dataclass (frozen=True)` — A node in a labeled ordered tree. Two nodes are
543 considered equal when their `content_id` values match. Used as `TreeInput.root`.
544
545 | Field | Type | Description |
546 |-------|------|-------------|
547 | `id` | `str` | Stable unique identifier for this node (persists across edits) |
548 | `label` | `str` | Human-readable name (element tag, AST node type) |
549 | `content_id` | `str` | SHA-256 of this node's own value, excluding children |
550 | `children` | `tuple[TreeNode, ...]` | Ordered recursive children tuple |
551
552 ---
553
554 ## OT Merge Types
555
556 **Path:** `muse/core/op_transform.py`
557
558 Operational Transformation types for the `StructuredMergePlugin` extension.
559
560 #### `MergeOpsResult`
561
562 `@dataclass` — Result of `merge_op_lists()`. Carries auto-merged ops and any
563 unresolvable conflicts as pairs.
564
565 | Field | Type | Default | Description |
566 |-------|------|---------|-------------|
567 | `merged_ops` | `list[DomainOp]` | `[]` | Operations that were auto-merged (commuting ops from both branches) |
568 | `conflict_ops` | `list[tuple[DomainOp, DomainOp]]` | `[]` | Pairs of non-commuting operations: `(our_op, their_op)` |
569
570 **Property:** `is_clean: bool` — `True` when `conflict_ops` is empty.
571
572 **Lattice contract:** `merged_ops` contains every auto-merged op exactly once;
573 `conflict_ops` contains every unresolvable pair exactly once.
574
575 ---
576
577 ## Op Log Types
578
579 **Path:** `muse/core/op_log.py`
580
581 The op log records every domain operation in causal order, enabling CRDT-style
582 replay and session-level undo.
583
584 #### `OpEntry`
585
586 `TypedDict` — One entry in the op log. Persisted as JSONL under
587 `.muse/op_log/<session_id>.jsonl`.
588
589 | Field | Type | Description |
590 |-------|------|-------------|
591 | `op_id` | `str` | UUID4; deduplication key |
592 | `actor_id` | `str` | Agent or human identity that produced this op |
593 | `lamport_ts` | `int` | Logical Lamport timestamp; monotonically increasing per session |
594 | `parent_op_ids` | `list[str]` | Causal parents — op IDs this op depends on |
595 | `domain` | `str` | Domain identifier (e.g. `"midi"`, `"code"`) |
596 | `domain_op` | `DomainOp` | The typed domain operation |
597 | `created_at` | `str` | ISO-8601 UTC timestamp |
598 | `intent_id` | `str` | Coordination intent linkage (`""` when not used) |
599 | `reservation_id` | `str` | Coordination reservation linkage (`""` when not used) |
600
601 #### `OpLogCheckpoint`
602
603 `TypedDict` — Snapshot of op-log state at a point in time. Allows replay to
604 start from the checkpoint rather than replaying the full log.
605
606 | Field | Type | Description |
607 |-------|------|-------------|
608 | `session_id` | `str` | Session identifier |
609 | `snapshot_id` | `str` | Muse snapshot ID capturing all ops to date |
610 | `lamport_ts` | `int` | Highest Lamport timestamp at checkpoint time |
611 | `op_count` | `int` | Total number of ops included up to this checkpoint |
612 | `created_at` | `str` | ISO-8601 UTC timestamp |
613
614 #### `OpLog`
615
616 Class (not a TypedDict). Session-scoped append-only log of `OpEntry` records.
617
618 | Method | Signature | Description |
619 |--------|-----------|-------------|
620 | `next_lamport_ts` | `() -> int` | Return and increment the Lamport clock |
621 | `append` | `(entry: OpEntry) -> None` | Append one entry to the log |
622 | `read_all` | `() -> list[OpEntry]` | Read every entry from disk |
623 | `replay_since_checkpoint` | `() -> list[OpEntry]` | Read only entries after the most recent checkpoint |
624 | `to_structured_delta` | `(domain: str) -> StructuredDelta` | Fold all ops into a single `StructuredDelta` |
625 | `checkpoint` | `(snapshot_id: str) -> OpLogCheckpoint` | Write a checkpoint and return it |
626 | `read_checkpoint` | `() -> OpLogCheckpoint \| None` | Load the most recent checkpoint, or `None` |
627 | `session_id` | `() -> str` | The session identifier |
628
629 ---
630
631 ## CRDT Primitive Types
632
633 **Path:** `muse/core/crdts/`
634
635 Every CRDT class exposes a `join()` method satisfying the lattice laws
636 (commutativity, associativity, idempotency). Each also has `to_dict()` /
637 `from_dict()` for serialisation to/from a named wire-format TypedDict.
638
639 ### VectorClock (`muse/core/crdts/vclock.py`)
640
641 #### `VClockDict` (Type Alias)
642
643 Wire format: `dict[str, int]` mapping agent ID → event count. Absent keys
644 default to 0. Produced by `VectorClock.to_dict()`.
645
646 #### `VectorClock`
647
648 Class — Partially-ordered logical clock tracking causal relationships across agents.
649
650 | Method | Signature | Description |
651 |--------|-----------|-------------|
652 | `increment` | `(agent_id: str) -> VectorClock` | Return a new clock with agent's count incremented |
653 | `merge` | `(other: VectorClock) -> VectorClock` | Lattice join: per-agent maximum |
654 | `happens_before` | `(other: VectorClock) -> bool` | `True` if `self` causally precedes `other` |
655 | `concurrent_with` | `(other: VectorClock) -> bool` | `True` if neither happens before the other |
656 | `equivalent` | `(other: VectorClock) -> bool` | Component-wise equality |
657 | `to_dict` | `() -> dict[str, int]` | Serialise to wire format |
658 | `from_dict` | `(data: dict[str, int]) -> VectorClock` | Deserialise from wire format (classmethod) |
659
660 ### LWWRegister (`muse/core/crdts/lww_register.py`)
661
662 #### `LWWValue`
663
664 `TypedDict` — Wire format for a Last-Write-Wins Register.
665
666 | Field | Type | Description |
667 |-------|------|-------------|
668 | `value` | `str` | Stored payload |
669 | `timestamp` | `float` | Unix seconds or logical clock value |
670 | `author` | `str` | Agent ID; used as lexicographic tiebreaker for equal timestamps |
671
672 #### `LWWRegister`
673
674 Class — Last-Write-Wins Register. Conflict-free: higher `(timestamp, author,
675 value)` tuple always wins.
676
677 | Method | Signature | Description |
678 |--------|-----------|-------------|
679 | `read` | `() -> str` | Return the current value |
680 | `write` | `(value, timestamp, author: str) -> LWWRegister` | Return a new register with the given value |
681 | `join` | `(other: LWWRegister) -> LWWRegister` | Lattice join: argmax on `(timestamp, author, value)` |
682 | `equivalent` | `(other: LWWRegister) -> bool` | All three fields equal |
683 | `to_dict` | `() -> LWWValue` | Serialise |
684 | `from_dict` | `(data: LWWValue) -> LWWRegister` | Deserialise (classmethod) |
685
686 ### ORSet (`muse/core/crdts/or_set.py`)
687
688 #### `ORSetEntry`
689
690 `TypedDict` — One observed-remove token in the ORSet.
691
692 | Field | Type | Description |
693 |-------|------|-------------|
694 | `element` | `str` | The string value tracked in the set |
695 | `token` | `str` | UUID4 unique to this specific addition event |
696
697 #### `ORSetDict`
698
699 `TypedDict` — Wire format for an Observed-Remove Set.
700
701 | Field | Type | Description |
702 |-------|------|-------------|
703 | `entries` | `list[ORSetEntry]` | All live addition events |
704 | `tombstones` | `list[str]` | Token strings that have been removed |
705
706 #### `ORSet`
707
708 Class — Observed-Remove Set. Add-wins: concurrent add and remove of the same
709 element results in the element being present.
710
711 | Method | Signature | Description |
712 |--------|-----------|-------------|
713 | `add` | `(element: str) -> tuple[ORSet, str]` | Add element, return `(new_set, token)` |
714 | `remove` | `(element: str, observed_tokens: set[str]) -> ORSet` | Remove all observed tokens for element |
715 | `join` | `(other: ORSet) -> ORSet` | Lattice join: union entries, union tombstones |
716 | `elements` | `() -> frozenset[str]` | Live elements (entries not in tombstones) |
717 | `tokens_for` | `(element: str) -> set[str]` | All live token IDs for an element |
718 | `equivalent` | `(other: ORSet) -> bool` | Same live elements |
719 | `to_dict` | `() -> ORSetDict` | Serialise |
720 | `from_dict` | `(data: ORSetDict) -> ORSet` | Deserialise (classmethod) |
721
722 ### RGA (`muse/core/crdts/rga.py`)
723
724 #### `RGAElement`
725
726 `TypedDict` — One element in an RGA sequence.
727
728 | Field | Type | Description |
729 |-------|------|-------------|
730 | `id` | `str` | `"{timestamp}@{author}"` — globally unique, stable after creation |
731 | `value` | `str` | Content hash referencing the object store |
732 | `deleted` | `bool` | `True` = tombstoned (logically removed, never physically deleted) |
733 | `parent_id` | `str \| None` | ID of the predecessor element; `None` = prepend to head |
734
735 #### `RGA`
736
737 Class — Replicated Growable Array. Provides a convergent ordered sequence;
738 concurrent inserts at the same position are deterministically ordered by
739 descending `element_id`.
740
741 | Method | Signature | Description |
742 |--------|-----------|-------------|
743 | `insert` | `(after_id: str \| None, value: str, *, element_id: str) -> RGA` | Insert value after `after_id` (or at head if `None`) |
744 | `delete` | `(element_id: str) -> RGA` | Tombstone an element |
745 | `join` | `(other: RGA) -> RGA` | Lattice join: union on IDs; deleted in either → deleted in result |
746 | `to_sequence` | `() -> list[str]` | Visible values in order (tombstones excluded) |
747 | `equivalent` | `(other: RGA) -> bool` | Same sequence including tombstone state |
748 | `to_dict` | `() -> list[RGAElement]` | Serialise |
749 | `from_dict` | `(data: list[RGAElement]) -> RGA` | Deserialise (classmethod) |
750
751 ### AWMap (`muse/core/crdts/aw_map.py`)
752
753 #### `AWMapEntry`
754
755 `TypedDict` — One observed-remove token for an AWMap key-value pair.
756
757 | Field | Type | Description |
758 |-------|------|-------------|
759 | `key` | `str` | Map key |
760 | `value` | `str` | Stored value for this token |
761 | `token` | `str` | UUID4 unique to this specific `set()` event |
762
763 #### `AWMapDict`
764
765 `TypedDict` — Wire format for an Add-Wins Map.
766
767 | Field | Type | Description |
768 |-------|------|-------------|
769 | `entries` | `list[AWMapEntry]` | All live set-events |
770 | `tombstones` | `list[str]` | Token strings that have been removed |
771
772 #### `AWMap`
773
774 Class — Add-Wins Map. Concurrent `set` and `remove` of the same key results in
775 the key being present. For concurrent sets to the same key, the entry with the
776 highest token (lexicographic) wins.
777
778 | Method | Signature | Description |
779 |--------|-----------|-------------|
780 | `set` | `(key: str, value: str) -> AWMap` | Set key→value; tombstones all previous tokens for key |
781 | `remove` | `(key: str) -> AWMap` | Tombstone all tokens for key |
782 | `join` | `(other: AWMap) -> AWMap` | Lattice join: union entries minus union tombstones |
783 | `get` | `(key: str) -> str \| None` | Current value for key; `None` if absent |
784 | `keys` | `() -> frozenset[str]` | All live keys |
785 | `to_plain_dict` | `() -> dict[str, str]` | Projection to a simple dict |
786 | `equivalent` | `(other: AWMap) -> bool` | Same live key→value mapping |
787 | `to_dict` | `() -> AWMapDict` | Serialise |
788 | `from_dict` | `(data: AWMapDict) -> AWMap` | Deserialise (classmethod) |
789
790 ### GCounter (`muse/core/crdts/g_counter.py`)
791
792 #### `GCounterDict` (Type Alias)
793
794 Wire format: `dict[str, int]` mapping agent ID → per-agent count. Absent keys
795 default to 0. Monotonically non-decreasing per slot.
796
797 #### `GCounter`
798
799 Class — Grow-only Counter. The global value is the sum of all per-agent counts.
800 `join` takes the per-slot maximum — the resulting counter is always ≥ both
801 inputs.
802
803 | Method | Signature | Description |
804 |--------|-----------|-------------|
805 | `increment` | `(agent_id: str, by: int = 1) -> GCounter` | Increment agent's slot |
806 | `join` | `(other: GCounter) -> GCounter` | Lattice join: per-slot maximum |
807 | `value` | `() -> int` | Sum of all per-agent counts |
808 | `value_for` | `(agent_id: str) -> int` | Count for a specific agent |
809 | `equivalent` | `(other: GCounter) -> bool` | All slots equal |
810 | `to_dict` | `() -> dict[str, int]` | Serialise |
811 | `from_dict` | `(data: dict[str, int]) -> GCounter` | Deserialise (classmethod) |
812
813 ---
814
815 ## Store Types
816
817 **Path:** `muse/core/store.py`
818
819 All commit and snapshot metadata is stored as JSON files under `.muse/`.
820 Wire-format `TypedDict`s are the JSON-serialisable shapes. In-memory
821 `dataclass`es are the rich representations used in business logic.
822
823 ### Wire-Format TypedDicts
824
825 #### `CommitDict`
826
827 `TypedDict (total=False)` — JSON-serialisable representation of a commit
828 record. All fields are optional to support reading older records that lack
829 newer fields. `from_dict()` populates defaults for absent fields.
830
831 | Field | Type | Description |
832 |-------|------|-------------|
833 | `commit_id` | `str` | SHA-256 hex digest of the commit's canonical inputs |
834 | `repo_id` | `str` | UUID identifying the repository |
835 | `branch` | `str` | Branch name at time of commit |
836 | `snapshot_id` | `str` | SHA-256 hex digest of the attached snapshot |
837 | `message` | `str` | Commit message |
838 | `committed_at` | `str` | ISO-8601 UTC timestamp |
839 | `parent_commit_id` | `str \| None` | First parent commit ID; `None` for initial commit |
840 | `parent2_commit_id` | `str \| None` | Second parent commit ID; non-`None` only for merge commits |
841 | `author` | `str` | Author name |
842 | `metadata` | `dict[str, str]` | Extensible string→string metadata bag |
843 | `structured_delta` | `StructuredDelta \| None` | Typed delta from plugin `diff()` at commit time; `None` for initial commit |
844 | `sem_ver_bump` | `SemVerBump` | Semantic version impact of this commit |
845 | `breaking_changes` | `list[str]` | Public addresses removed or incompatibly changed |
846 | `agent_id` | `str` | Stable identity string for the committing agent or human |
847 | `model_id` | `str` | Model identifier when author is an AI agent; `""` for human |
848 | `toolchain_id` | `str` | Toolchain that produced the commit |
849 | `prompt_hash` | `str` | SHA-256 of the instruction/prompt (privacy-preserving) |
850 | `signature` | `str` | HMAC-SHA256 hex digest of `commit_id` using agent's shared key |
851 | `signer_key_id` | `str` | Fingerprint of the signing key (SHA-256[:16] of raw key bytes) |
852 | `format_version` | `int` | Schema evolution counter (1–5); see version table below |
853 | `reviewed_by` | `list[str]` | ORSet logical state: list of reviewer IDs (format_version ≥ 5) |
854 | `test_runs` | `int` | GCounter: monotonically increasing test-run count (format_version ≥ 5) |
855
856 **`format_version` evolution table:**
857
858 | Version | Fields added |
859 |---------|-------------|
860 | 1 | `commit_id`, `snapshot_id`, `parent_commit_id`, `message`, `author` |
861 | 2 | `structured_delta` |
862 | 3 | `sem_ver_bump`, `breaking_changes` |
863 | 4 | `agent_id`, `model_id`, `toolchain_id`, `prompt_hash`, `signature`, `signer_key_id` |
864 | 5 | `reviewed_by` (ORSet), `test_runs` (GCounter) |
865
866 #### `SnapshotDict`
867
868 `TypedDict` — JSON-serialisable representation of a snapshot record.
869
870 | Field | Type | Description |
871 |-------|------|-------------|
872 | `snapshot_id` | `str` | SHA-256 hex digest of the manifest |
873 | `manifest` | `dict[str, str]` | POSIX path → SHA-256 object digest |
874 | `created_at` | `str` | ISO-8601 UTC timestamp |
875
876 #### `TagDict`
877
878 `TypedDict` — JSON-serialisable representation of a semantic tag.
879
880 | Field | Type | Description |
881 |-------|------|-------------|
882 | `tag_id` | `str` | UUID identifying the tag |
883 | `repo_id` | `str` | UUID identifying the repository |
884 | `commit_id` | `str` | SHA-256 commit ID this tag points to |
885 | `tag` | `str` | Tag name string (e.g. `"v1.0"`) |
886 | `created_at` | `str` | ISO-8601 UTC timestamp |
887
888 #### `RemoteCommitPayload`
889
890 `TypedDict (total=False)` — Wire format received from a remote during push/pull.
891 All fields optional; callers validate required fields before constructing a
892 `CommitRecord`.
893
894 | Field | Type | Description |
895 |-------|------|-------------|
896 | `commit_id` | `str` | Commit identifier |
897 | `repo_id` | `str` | Repository UUID |
898 | `branch` | `str` | Branch name |
899 | `snapshot_id` | `str` | Snapshot identifier |
900 | `message` | `str` | Commit message |
901 | `committed_at` | `str` | ISO-8601 timestamp |
902 | `parent_commit_id` | `str \| None` | First parent |
903 | `parent2_commit_id` | `str \| None` | Second parent (merge commits) |
904 | `author` | `str` | Author name |
905 | `metadata` | `dict[str, str]` | Metadata bag |
906 | `manifest` | `dict[str, str]` | Inline snapshot manifest (remote optimisation) |
907
908 ### In-Memory Dataclasses
909
910 #### `CommitRecord`
911
912 `@dataclass` — In-memory representation of a commit. All datetime fields are
913 `datetime.datetime` objects with UTC timezone.
914
915 | Field | Type | Default | Description |
916 |-------|------|---------|-------------|
917 | `commit_id` | `str` | required | SHA-256 hex digest |
918 | `repo_id` | `str` | required | Repository UUID |
919 | `branch` | `str` | required | Branch name |
920 | `snapshot_id` | `str` | required | Attached snapshot digest |
921 | `message` | `str` | required | Commit message |
922 | `committed_at` | `datetime.datetime` | required | UTC commit timestamp |
923 | `parent_commit_id` | `str \| None` | `None` | First parent; `None` for root commits |
924 | `parent2_commit_id` | `str \| None` | `None` | Second parent for merge commits |
925 | `author` | `str` | `""` | Author name |
926 | `metadata` | `dict[str, str]` | `{}` | Extensible string→string metadata |
927 | `structured_delta` | `StructuredDelta \| None` | `None` | Typed delta from commit time |
928 | `sem_ver_bump` | `SemVerBump` | `"none"` | Semantic version impact |
929 | `breaking_changes` | `list[str]` | `[]` | Removed/incompatible public symbol addresses |
930 | `agent_id` | `str` | `""` | Agent or human identity |
931 | `model_id` | `str` | `""` | AI model identifier; `""` for human |
932 | `toolchain_id` | `str` | `""` | Toolchain identifier |
933 | `prompt_hash` | `str` | `""` | SHA-256 of the prompt |
934 | `signature` | `str` | `""` | HMAC-SHA256 of `commit_id` |
935 | `signer_key_id` | `str` | `""` | Signing key fingerprint |
936 | `format_version` | `int` | `5` | Schema evolution counter |
937 | `reviewed_by` | `list[str]` | `[]` | ORSet of reviewer IDs |
938 | `test_runs` | `int` | `0` | GCounter of test-run events |
939
940 **Methods:**
941
942 | Method | Returns | Description |
943 |--------|---------|-------------|
944 | `to_dict()` | `CommitDict` | Serialise to JSON-ready TypedDict |
945 | `from_dict(d)` | `CommitRecord` | Deserialise from JSON-loaded TypedDict (classmethod) |
946
947 #### `SnapshotRecord`
948
949 `@dataclass` — In-memory representation of a content-addressed snapshot.
950
951 | Field | Type | Default | Description |
952 |-------|------|---------|-------------|
953 | `snapshot_id` | `str` | required | SHA-256 hex digest of the manifest |
954 | `manifest` | `dict[str, str]` | required | POSIX path → SHA-256 object digest |
955 | `created_at` | `datetime.datetime` | UTC now | Creation timestamp |
956
957 **Methods:** `to_dict() -> SnapshotDict`, `from_dict(d: SnapshotDict) -> SnapshotRecord`
958
959 #### `TagRecord`
960
961 `@dataclass` — In-memory representation of a semantic tag.
962
963 | Field | Type | Default | Description |
964 |-------|------|---------|-------------|
965 | `tag_id` | `str` | required | UUID |
966 | `repo_id` | `str` | required | Repository UUID |
967 | `commit_id` | `str` | required | Tagged commit's SHA-256 digest |
968 | `tag` | `str` | required | Tag name |
969 | `created_at` | `datetime.datetime` | UTC now | Creation timestamp |
970
971 **Methods:** `to_dict() -> TagDict`, `from_dict(d: TagDict) -> TagRecord`
972
973 ---
974
975 ## Merge Engine Types
976
977 **Path:** `muse/core/merge_engine.py`
978
979 #### `MergeStatePayload`
980
981 `TypedDict (total=False)` — JSON-serialisable form of an in-progress merge
982 state. Written to `.muse/MERGE_STATE.json` when a merge has unresolved
983 conflicts.
984
985 | Field | Type | Description |
986 |-------|------|-------------|
987 | `base_commit` | `str` | Common ancestor commit ID |
988 | `ours_commit` | `str` | Current branch HEAD at merge start |
989 | `theirs_commit` | `str` | Incoming branch HEAD at merge start |
990 | `conflict_paths` | `list[str]` | POSIX paths with unresolved conflicts |
991 | `other_branch` | `str` | Name of the branch being merged in (optional) |
992
993 #### `MergeState`
994
995 `@dataclass (frozen=True)` — Loaded in-memory representation of
996 `MERGE_STATE.json`. Immutable to prevent accidental mutation during resolution.
997
998 | Field | Type | Default | Description |
999 |-------|------|---------|-------------|
1000 | `conflict_paths` | `list[str]` | `[]` | Paths with unresolved conflicts |
1001 | `base_commit` | `str \| None` | `None` | Common ancestor commit ID |
1002 | `ours_commit` | `str \| None` | `None` | Our HEAD at merge start |
1003 | `theirs_commit` | `str \| None` | `None` | Their HEAD at merge start |
1004 | `other_branch` | `str \| None` | `None` | Name of the incoming branch |
1005
1006 ---
1007
1008 ## Attributes Types
1009
1010 **Path:** `muse/core/attributes.py`
1011
1012 Parse and resolve `.museattributes` TOML merge-strategy rules.
1013
1014 #### `VALID_STRATEGIES`
1015
1016 `frozenset[str]` — The set of legal strategy strings:
1017 `{"ours", "theirs", "union", "auto", "manual"}`.
1018
1019 #### `AttributesMeta`
1020
1021 `TypedDict (total=False)` — The `[meta]` section of `.museattributes`.
1022
1023 | Field | Type | Description |
1024 |-------|------|-------------|
1025 | `domain` | `str` | Domain name this file targets (optional) |
1026
1027 #### `AttributesRuleDict`
1028
1029 `TypedDict` — A single `[[rules]]` entry as parsed from TOML.
1030
1031 | Field | Type | Description |
1032 |-------|------|-------------|
1033 | `path` | `str` | `fnmatch` glob matched against workspace-relative POSIX paths |
1034 | `dimension` | `str` | Domain axis name (e.g. `"notes"`) or `"*"` to match all |
1035 | `strategy` | `str` | One of the `VALID_STRATEGIES` strings |
1036
1037 #### `MuseAttributesFile`
1038
1039 `TypedDict (total=False)` — The complete `.museattributes` file structure after
1040 TOML parsing.
1041
1042 | Field | Type | Description |
1043 |-------|------|-------------|
1044 | `meta` | `AttributesMeta` | Optional `[meta]` section |
1045 | `rules` | `list[AttributesRuleDict]` | Ordered `[[rules]]` array |
1046
1047 #### `AttributeRule`
1048
1049 `@dataclass (frozen=True)` — A single resolved rule from `.museattributes`.
1050
1051 | Field | Type | Default | Description |
1052 |-------|------|---------|-------------|
1053 | `path_pattern` | `str` | required | `fnmatch` glob matched against workspace-relative POSIX paths |
1054 | `dimension` | `str` | required | Domain axis name (e.g. `"notes"`, `"pitch_bend"`) or `"*"` to match all |
1055 | `strategy` | `str` | required | One of the `VALID_STRATEGIES` strings |
1056 | `source_index` | `int` | `0` | 0-based index of the rule in the `[[rules]]` array |
1057
1058 **Public functions:**
1059
1060 - `load_attributes(root, *, domain=None) -> list[AttributeRule]` — reads
1061 `.museattributes` TOML, validates domain, returns rules in file order.
1062 - `read_attributes_meta(root) -> AttributesMeta` — returns the `[meta]` section only.
1063 - `resolve_strategy(rules, path, dimension="*") -> str` — first-match lookup;
1064 returns `"auto"` when no rule matches.
1065
1066 ---
1067
1068 ## MIDI Dimension Merge Types
1069
1070 **Path:** `muse/plugins/midi/midi_merge.py`
1071
1072 The multidimensional merge engine for the MIDI domain. MIDI events are bucketed
1073 into 21 orthogonal dimension slices; each slice has a content hash for fast
1074 change detection. A three-way merge resolves each dimension independently using
1075 `.museattributes` strategies, then reconstructs a valid MIDI file.
1076
1077 #### Constants
1078
1079 | Name | Type | Description |
1080 |------|------|-------------|
1081 | `INTERNAL_DIMS` | `list[str]` | 21 canonical dimension names in priority order: `"tempo_map"`, `"time_signatures"`, `"key_signatures"`, `"track_structure"`, `"notes"`, `"pitch_bend"`, `"channel_pressure"`, `"poly_pressure"`, `"cc_modulation"`, `"cc_volume"`, `"cc_pan"`, `"cc_expression"`, `"cc_sustain"`, `"cc_portamento"`, `"cc_sostenuto"`, `"cc_soft_pedal"`, `"cc_reverb"`, `"cc_chorus"`, `"program_change"`, `"markers"`, `"track_names"` |
1082 | `NON_INDEPENDENT_DIMS` | `frozenset[str]` | Dimensions that block the entire merge on bilateral conflict: `{"tempo_map", "time_signatures", "track_structure"}` |
1083 | `DIM_ALIAS` | `dict[str, str]` | User-facing names → internal bucket names |
1084
1085 #### `_MsgVal` (Type Alias)
1086
1087 `type _MsgVal = int | str | list[int]` — The set of value types that appear
1088 in the serialised form of a MIDI message field. Avoids `dict[str, object]`.
1089
1090 #### `DimensionSlice`
1091
1092 `@dataclass` — All MIDI events belonging to one dimension of a parsed file.
1093
1094 | Field | Type | Default | Description |
1095 |-------|------|---------|-------------|
1096 | `name` | `str` | required | Internal dimension name (e.g. `"notes"`) |
1097 | `events` | `list[tuple[int, mido.Message]]` | `[]` | `(abs_tick, message)` pairs sorted by ascending absolute tick |
1098 | `content_hash` | `str` | `""` | SHA-256 of the canonical JSON serialisation; computed in `__post_init__` |
1099
1100 #### `MidiDimensions`
1101
1102 `@dataclass` — All 21 dimension slices extracted from one MIDI file, plus
1103 file-level metadata.
1104
1105 | Field | Type | Description |
1106 |-------|------|-------------|
1107 | `ticks_per_beat` | `int` | MIDI timing resolution (pulses per quarter note) |
1108 | `file_type` | `int` | MIDI file type (0 = single-track, 1 = multi-track synchronous) |
1109 | `slices` | `dict[str, DimensionSlice]` | Internal dimension name → slice |
1110
1111 **Method:**
1112
1113 | Name | Signature | Description |
1114 |------|-----------|-------------|
1115 | `get` | `(dim: str) -> DimensionSlice` | Resolve a user-facing alias or internal name to the correct slice |
1116
1117 **Public functions:**
1118
1119 | Function | Signature | Description |
1120 |----------|-----------|-------------|
1121 | `extract_dimensions` | `(midi_bytes: bytes) -> MidiDimensions` | Parse MIDI bytes and bucket events by dimension |
1122 | `dimension_conflict_detail` | `(base, left, right: MidiDimensions) -> dict[str, str]` | Per-dimension change report: `"unchanged"`, `"left_only"`, `"right_only"`, or `"both"` |
1123 | `merge_midi_dimensions` | `(base_bytes, left_bytes, right_bytes: bytes, attrs_rules: list[AttributeRule], path: str) -> tuple[bytes, dict[str, str]] \| None` | Three-way dimension merge; returns `(merged_bytes, dimension_report)` or `None` on unresolvable conflict |
1124
1125 ---
1126
1127 ## Code Plugin Types
1128
1129 **Path:** `muse/plugins/code/`
1130
1131 The code domain plugin introduces typed representations for source code
1132 symbols parsed from Python (via `ast`) and other languages (via tree-sitter).
1133
1134 #### `SymbolKind` (Type Alias)
1135
1136 ```python
1137 SymbolKind = Literal[
1138 "function", "async_function", "class", "method",
1139 "async_method", "variable", "import"
1140 ]
1141 ```
1142
1143 #### `SymbolRecord`
1144
1145 `TypedDict` — Metadata for one named symbol extracted from a source file.
1146 The hashing strategy uses multiple content IDs so that rename detection,
1147 signature-only changes, and full body changes can each be distinguished.
1148
1149 | Field | Type | Description |
1150 |-------|------|-------------|
1151 | `kind` | `SymbolKind` | Symbol kind discriminator |
1152 | `name` | `str` | Short symbol name (e.g. `"calculate"`) |
1153 | `qualified_name` | `str` | Dotted name within the file (e.g. `"MyClass.calculate"`) |
1154 | `content_id` | `str` | SHA-256 of full normalised AST (name + signature + body) |
1155 | `body_hash` | `str` | SHA-256 of body statements only — for rename detection |
1156 | `signature_id` | `str` | SHA-256 of `"name(args)->return"` — for implementation-only changes |
1157 | `metadata_id` | `str` | SHA-256 of decorators/async/bases; `""` for pre-v2 records |
1158 | `canonical_key` | `str` | `"{file}#{scope}#{kind}#{name}#{lineno}"` — stable handle across renames |
1159 | `lineno` | `int` | Line number of the symbol definition |
1160 | `end_lineno` | `int` | Last line number of the symbol definition |
1161
1162 #### `SymbolTree` (Type Alias)
1163
1164 `TypeAlias = dict[str, SymbolRecord]` — Maps `DomainAddress` strings to
1165 `SymbolRecord` instances for all symbols in a file.
1166
1167 #### `LanguageAdapter`
1168
1169 `@runtime_checkable Protocol` — Abstraction over language-specific AST parsing.
1170 Implemented by `PythonAdapter`, `TreeSitterAdapter`, and `FallbackAdapter`.
1171
1172 | Method | Signature | Description |
1173 |--------|-----------|-------------|
1174 | `supported_extensions` | `() -> frozenset[str]` | File extensions this adapter handles (e.g. `{".py"}`) |
1175 | `parse_symbols` | `(source: bytes, file_path: str) -> SymbolTree` | Parse source bytes and return a `SymbolTree` |
1176 | `file_content_id` | `(source: bytes) -> str` | SHA-256 of the entire file (fallback content address) |
1177
1178 #### `LangSpec`
1179
1180 `TypedDict` — Tree-sitter per-language configuration used to construct a
1181 `TreeSitterAdapter`.
1182
1183 | Field | Type | Description |
1184 |-------|------|-------------|
1185 | `extensions` | `frozenset[str]` | File extensions for this language |
1186 | `module_name` | `str` | Python import name (e.g. `"tree_sitter_javascript"`) |
1187 | `lang_func` | `str` | Attribute name on the module returning the raw grammar capsule |
1188 | `query_str` | `str` | Tree-sitter S-expression query with `@sym` and `@name` captures |
1189 | `kind_map` | `dict[str, SymbolKind]` | Tree-sitter node type → `SymbolKind` |
1190 | `class_node_types` | `frozenset[str]` | Node types treated as class containers |
1191 | `class_name_field` | `str` | Field name that carries the class identifier node |
1192 | `receiver_capture` | `str` | Go-style receiver capture name; `""` to skip |
1193
1194 **Concrete adapter classes** (not TypedDicts):
1195 - `PythonAdapter` — implements `LanguageAdapter` via `ast.parse` / `ast.unparse`
1196 - `FallbackAdapter(extensions)` — returns empty `SymbolTree`, raw-bytes SHA-256
1197 - `TreeSitterAdapter(spec, parser, language)` — tree-sitter CST, error-tolerant
1198
1199 ---
1200
1201 ## Configuration Types
1202
1203 **Path:** `muse/cli/config.py`
1204
1205 The structured view of `.muse/config.toml`.
1206
1207 #### `AuthEntry`
1208
1209 `TypedDict (total=False)` — `[auth]` section.
1210
1211 | Field | Type | Description |
1212 |-------|------|-------------|
1213 | `token` | `str` | Bearer token for Muse Hub authentication. **Never logged.** |
1214
1215 #### `RemoteEntry`
1216
1217 `TypedDict (total=False)` — `[remotes.<name>]` section.
1218
1219 | Field | Type | Description |
1220 |-------|------|-------------|
1221 | `url` | `str` | Remote Hub URL |
1222 | `branch` | `str` | Upstream branch tracked by this remote |
1223
1224 #### `MuseConfig`
1225
1226 `TypedDict (total=False)` — Structured view of the entire `.muse/config.toml`.
1227
1228 | Field | Type | Description |
1229 |-------|------|-------------|
1230 | `auth` | `AuthEntry` | Authentication credentials section |
1231 | `remotes` | `dict[str, RemoteEntry]` | Named remote sections |
1232 | `domain` | `dict[str, str]` | Domain-specific key/value pairs; ignored by core, read only by the active plugin (e.g. `ticks_per_beat` for music) |
1233
1234 #### `RemoteConfig`
1235
1236 `TypedDict` — Public-facing remote descriptor returned by `list_remotes()`.
1237
1238 | Field | Type | Description |
1239 |-------|------|-------------|
1240 | `name` | `str` | Remote name (e.g. `"origin"`) |
1241 | `url` | `str` | Remote URL |
1242
1243 ---
1244
1245 ## MIDI / MusicXML Import Types
1246
1247 **Path:** `muse/cli/midi_parser.py`
1248
1249 Types used by `muse import` to parse Standard MIDI Files and MusicXML documents
1250 into Muse's internal note representation.
1251
1252 #### `MidiMeta`
1253
1254 `TypedDict` — Format-specific metadata for Standard MIDI Files.
1255
1256 | Field | Type | Description |
1257 |-------|------|-------------|
1258 | `num_tracks` | `int` | Number of MIDI tracks in the file |
1259
1260 #### `MusicXMLMeta`
1261
1262 `TypedDict` — Format-specific metadata for MusicXML files.
1263
1264 | Field | Type | Description |
1265 |-------|------|-------------|
1266 | `num_parts` | `int` | Number of parts (instruments) in the score |
1267 | `part_names` | `list[str]` | Display names of each part |
1268
1269 #### `RawMeta` (Type Alias)
1270
1271 `TypeAlias = MidiMeta | MusicXMLMeta` — Discriminated union of all
1272 format-specific metadata shapes.
1273
1274 #### `NoteEvent`
1275
1276 `@dataclass` — A single sounding note extracted from an imported file.
1277
1278 | Field | Type | Description |
1279 |-------|------|-------------|
1280 | `pitch` | `int` | MIDI pitch number (0–127) |
1281 | `velocity` | `int` | MIDI velocity (0–127; 0 = note-off) |
1282 | `start_tick` | `int` | Onset tick relative to file start |
1283 | `duration_ticks` | `int` | Note length in MIDI ticks |
1284 | `channel` | `int` | MIDI channel (0–15) |
1285 | `channel_name` | `str` | Track/part name for this channel |
1286
1287 #### `MuseImportData`
1288
1289 `@dataclass` — Complete parsed result returned by `parse_file()`.
1290
1291 | Field | Type | Description |
1292 |-------|------|-------------|
1293 | `source_path` | `pathlib.Path` | Absolute path to the source file |
1294 | `format` | `str` | `"midi"` or `"musicxml"` |
1295 | `ticks_per_beat` | `int` | MIDI timing resolution (pulses per quarter note) |
1296 | `tempo_bpm` | `float` | Tempo in beats per minute |
1297 | `notes` | `list[NoteEvent]` | All sounding notes, in onset order |
1298 | `tracks` | `list[str]` | Track/part names present in the file |
1299 | `raw_meta` | `RawMeta` | Format-specific metadata |
1300
1301 ---
1302
1303 ## Stash Types
1304
1305 **Path:** `muse/cli/commands/stash.py`
1306
1307 #### `StashEntry`
1308
1309 `TypedDict` — A single entry in the stash stack, persisted to
1310 `.muse/stash.json` as one element of a JSON array. Push prepends; pop
1311 removes index 0.
1312
1313 | Field | Type | Description |
1314 |-------|------|-------------|
1315 | `snapshot_id` | `str` | SHA-256 content digest of the stashed snapshot |
1316 | `manifest` | `dict[str, str]` | POSIX path → SHA-256 object digest of stashed files |
1317 | `branch` | `str` | Branch name active when the stash was saved |
1318 | `stashed_at` | `str` | ISO-8601 UTC timestamp of the stash operation |
1319
1320 ---
1321
1322 ## Error Hierarchy
1323
1324 **Path:** `muse/core/errors.py`
1325
1326 #### `ExitCode`
1327
1328 `IntEnum` — Standardised CLI exit codes.
1329
1330 | Value | Integer | Meaning |
1331 |-------|---------|---------|
1332 | `SUCCESS` | `0` | Command completed successfully |
1333 | `USER_ERROR` | `1` | Bad arguments or invalid user input |
1334 | `REPO_NOT_FOUND` | `2` | Not inside a Muse repository |
1335 | `INTERNAL_ERROR` | `3` | Unexpected internal failure |
1336
1337 #### `MuseCLIError`
1338
1339 `Exception` — Base exception for all Muse CLI errors.
1340
1341 | Field | Type | Description |
1342 |-------|------|-------------|
1343 | `exit_code` | `ExitCode` | Exit code to use when this exception terminates the process |
1344
1345 #### `RepoNotFoundError`
1346
1347 `MuseCLIError` — Raised when a command is invoked outside a Muse repository.
1348 Default message: `"Not a Muse repository. Run muse init."` Default exit
1349 code: `ExitCode.REPO_NOT_FOUND`.
1350
1351 **Alias:** `MuseNotARepoError = RepoNotFoundError`
1352
1353 ---
1354
1355 ## Entity Hierarchy
1356
1357 ```
1358 Muse VCS
1359
1360 ├── Domain Protocol (muse/domain.py)
1361 │ │
1362 │ ├── Type Aliases
1363 │ │ ├── DomainAddress — str
1364 │ │ ├── SemVerBump — Literal["major","minor","patch","none"]
1365 │ │ ├── LeafDomainOp — InsertOp | DeleteOp | MoveOp | ReplaceOp | MutateOp
1366 │ │ ├── DomainOp — LeafDomainOp | PatchOp (recursive via PatchOp.child_ops)
1367 │ │ ├── LiveState — SnapshotManifest | pathlib.Path
1368 │ │ ├── StateSnapshot — SnapshotManifest
1369 │ │ └── StateDelta — StructuredDelta
1370 │ │
1371 │ ├── Operation TypedDicts
1372 │ │ ├── InsertOp — op="insert" · address · position · content_id · content_summary
1373 │ │ ├── DeleteOp — op="delete" · address · position · content_id · content_summary
1374 │ │ ├── MoveOp — op="move" · address · from_position · to_position · content_id
1375 │ │ ├── ReplaceOp — op="replace" · address · position · old/new content_id · summaries
1376 │ │ ├── FieldMutation — old: str · new: str (used as dict[str,FieldMutation] in MutateOp)
1377 │ │ ├── MutateOp — op="mutate" · address · entity_id · old/new content_id · fields · summaries
1378 │ │ └── PatchOp — op="patch" · address · child_ops: list[DomainOp] · child_domain
1379 │ │
1380 │ ├── Snapshot / Delta TypedDicts
1381 │ │ ├── SnapshotManifest — {files: dict[str,str], domain: str}
1382 │ │ ├── StructuredDelta — total=False: domain · ops · summary · sem_ver_bump · breaking_changes
1383 │ │ ├── CRDTSnapshotManifest — files · domain · vclock · crdt_state · schema_version
1384 │ │ └── EntityProvenance — total=False: entity_id · origin_op_id · last_modified_op_id · …
1385 │ │
1386 │ ├── Result Dataclasses
1387 │ │ ├── ConflictRecord — path · conflict_type · summaries · addresses
1388 │ │ ├── MergeResult — merged · conflicts · applied_strategies · dimension_reports · op_log · conflict_records
1389 │ │ └── DriftReport — has_drift · summary · delta
1390 │ │
1391 │ └── Protocols
1392 │ ├── MuseDomainPlugin — @runtime_checkable: snapshot/diff/merge/drift/apply/schema
1393 │ ├── StructuredMergePlugin — extends MuseDomainPlugin: +merge_ops()
1394 │ └── CRDTPlugin — extends MuseDomainPlugin: +crdt_schema/join/to_crdt_state/from_crdt_state
1395
1396 ├── Domain Schema (muse/core/schema.py)
1397 │ ├── ElementSchema (type alias)
1398 │ │ ├── SequenceSchema — kind="sequence" · element_type · identity · diff_algorithm · alphabet
1399 │ │ ├── TreeSchema — kind="tree" · node_type · diff_algorithm
1400 │ │ ├── TensorSchema — kind="tensor" · dtype · rank · epsilon · diff_mode
1401 │ │ ├── SetSchema — kind="set" · element_type · identity
1402 │ │ └── MapSchema — kind="map" · key_type · value_schema: ElementSchema (recursive)
1403 │ ├── CRDTPrimitive — Literal["lww_register","or_set","rga","aw_map","g_counter"]
1404 │ ├── DimensionSpec — name · description · schema: ElementSchema · independent_merge
1405 │ ├── CRDTDimensionSpec — name · description · crdt_type: CRDTPrimitive · independent_merge
1406 │ └── DomainSchema — domain · description · dimensions · top_level · merge_mode · schema_version
1407
1408 ├── Diff Algorithms (muse/core/diff_algorithms/)
1409 │ ├── DiffInput (type alias)
1410 │ │ ├── SequenceInput — kind="sequence" · items: list[str]
1411 │ │ ├── SetInput — kind="set" · items: frozenset[str]
1412 │ │ ├── TensorInput — kind="tensor" · values: list[float]
1413 │ │ ├── MapInput — kind="map" · entries: dict[str,str]
1414 │ │ └── TreeInput — kind="tree" · root: TreeNode
1415 │ ├── EditKind — Literal["keep","insert","delete"]
1416 │ ├── EditStep — frozen dataclass: kind · base_index · target_index · item
1417 │ └── TreeNode — frozen dataclass: id · label · content_id · children: tuple[TreeNode,…]
1418
1419 ├── OT Merge (muse/core/op_transform.py)
1420 │ └── MergeOpsResult — merged_ops: list[DomainOp] · conflict_ops: list[tuple[DomainOp,DomainOp]]
1421
1422 ├── Op Log (muse/core/op_log.py)
1423 │ ├── OpEntry — TypedDict: op_id · actor_id · lamport_ts · parent_op_ids · domain · domain_op · …
1424 │ ├── OpLogCheckpoint — TypedDict: session_id · snapshot_id · lamport_ts · op_count · created_at
1425 │ └── OpLog — class: append/read_all/replay_since_checkpoint/checkpoint
1426
1427 ├── CRDT Primitives (muse/core/crdts/)
1428 │ ├── VectorClock — class: increment/merge/happens_before/concurrent_with ↔ dict[str,int]
1429 │ ├── LWWRegister — class: read/write/join ↔ LWWValue{value,timestamp,author}
1430 │ ├── ORSet — class: add/remove/join/elements ↔ ORSetDict{entries:list[ORSetEntry],tombstones}
1431 │ ├── RGA — class: insert/delete/join/to_sequence ↔ list[RGAElement{id,value,deleted,parent_id}]
1432 │ ├── AWMap — class: set/remove/join/get/keys ↔ AWMapDict{entries:list[AWMapEntry],tombstones}
1433 │ └── GCounter — class: increment/join/value ↔ dict[str,int]
1434
1435 ├── Store (muse/core/store.py)
1436 │ ├── Wire-Format TypedDicts
1437 │ │ ├── CommitDict — total=False: all commit fields · sem_ver_bump · agent provenance · CRDT annotations
1438 │ │ ├── SnapshotDict — snapshot_id · manifest · created_at
1439 │ │ ├── TagDict — tag_id · repo_id · commit_id · tag · created_at
1440 │ │ └── RemoteCommitPayload — total=False: wire commit + inline manifest
1441 │ └── In-Memory Dataclasses
1442 │ ├── CommitRecord — typed datetime · structured_delta · sem_ver_bump · provenance · reviewed_by/test_runs
1443 │ ├── SnapshotRecord — snapshot_id · manifest: dict[str,str] · datetime
1444 │ └── TagRecord — tag_id · repo_id · commit_id · tag · datetime
1445
1446 ├── Merge Engine (muse/core/merge_engine.py)
1447 │ ├── MergeStatePayload — total=False: MERGE_STATE.json shape
1448 │ └── MergeState — frozen dataclass: conflict_paths · base/ours/theirs commits · other_branch
1449
1450 ├── Attributes (muse/core/attributes.py)
1451 │ ├── VALID_STRATEGIES — frozenset{ours,theirs,union,auto,manual}
1452 │ ├── AttributesMeta — total=False: domain: str
1453 │ ├── AttributesRuleDict — path · dimension · strategy
1454 │ ├── MuseAttributesFile — total=False: meta + rules
1455 │ └── AttributeRule — frozen dataclass: path_pattern · dimension · strategy · source_index
1456
1457 ├── MIDI Dimension Merge (muse/plugins/midi/midi_merge.py)
1458 │ ├── INTERNAL_DIMS — list[str]: 21 dimension names
1459 │ ├── NON_INDEPENDENT_DIMS — frozenset{tempo_map,time_signatures,track_structure}
1460 │ ├── DIM_ALIAS — dict[str,str]: user alias → internal name
1461 │ ├── _MsgVal — type alias: int | str | list[int]
1462 │ ├── DimensionSlice — dataclass: name · events · content_hash
1463 │ └── MidiDimensions — dataclass: ticks_per_beat · file_type · slices: dict[str,DimensionSlice]
1464
1465 ├── Code Plugin (muse/plugins/code/)
1466 │ ├── SymbolKind — Literal["function","async_function","class","method",…]
1467 │ ├── SymbolRecord — TypedDict: kind · name · qualified_name · content_id · body_hash · signature_id · …
1468 │ ├── SymbolTree — type alias: dict[str, SymbolRecord]
1469 │ ├── LanguageAdapter — Protocol: supported_extensions/parse_symbols/file_content_id
1470 │ └── LangSpec — TypedDict: extensions · module_name · lang_func · query_str · kind_map · …
1471
1472 ├── Configuration (muse/cli/config.py)
1473 │ ├── AuthEntry — total=False: token: str
1474 │ ├── RemoteEntry — total=False: url · branch
1475 │ ├── MuseConfig — total=False: auth · remotes · domain: dict[str,str]
1476 │ └── RemoteConfig — name · url
1477
1478 ├── MIDI / MusicXML Import (muse/cli/midi_parser.py)
1479 │ ├── MidiMeta — num_tracks: int
1480 │ ├── MusicXMLMeta — num_parts · part_names
1481 │ ├── RawMeta — MidiMeta | MusicXMLMeta
1482 │ ├── NoteEvent — pitch · velocity · start_tick · duration_ticks · channel · channel_name
1483 │ └── MuseImportData — source_path · format · ticks_per_beat · tempo_bpm · notes · tracks · raw_meta
1484
1485 ├── Stash (muse/cli/commands/stash.py)
1486 │ └── StashEntry — snapshot_id · manifest · branch · stashed_at
1487
1488 └── Errors (muse/core/errors.py)
1489 ├── ExitCode — IntEnum: SUCCESS=0 USER_ERROR=1 REPO_NOT_FOUND=2 INTERNAL_ERROR=3
1490 ├── MuseCLIError — Exception: exit_code: ExitCode
1491 ├── RepoNotFoundError — MuseCLIError: default REPO_NOT_FOUND
1492 └── MuseNotARepoError — alias for RepoNotFoundError
1493 ```
1494
1495 ---
1496
1497 ## Entity Graphs (Mermaid)
1498
1499 Arrow conventions:
1500 - `*--` composition (owns, lifecycle-coupled)
1501 - `-->` association (references by ID or field)
1502 - `..>` dependency (uses, produces, or implements)
1503
1504 ---
1505
1506 ### Diagram 1 — Domain Protocol and Plugin Contract
1507
1508 The `MuseDomainPlugin` protocol and the types that flow through its six methods.
1509 Optional extensions `StructuredMergePlugin` and `CRDTPlugin` unlock richer
1510 semantics. `MidiPlugin` is the reference implementation.
1511
1512 ```mermaid
1513 classDiagram
1514 class SnapshotManifest {
1515 <<TypedDict>>
1516 +files : dict~str, str~
1517 +domain : str
1518 }
1519 class StructuredDelta {
1520 <<TypedDict total=False>>
1521 +domain : str
1522 +ops : list~DomainOp~
1523 +summary : str
1524 +sem_ver_bump : SemVerBump
1525 +breaking_changes : list~str~
1526 }
1527 class MergeResult {
1528 <<dataclass>>
1529 +merged : StateSnapshot
1530 +conflicts : list~str~
1531 +applied_strategies : dict~str, str~
1532 +dimension_reports : dict~str, dict~str, str~~
1533 +op_log : list~DomainOp~
1534 +conflict_records : list~ConflictRecord~
1535 +is_clean : bool
1536 }
1537 class ConflictRecord {
1538 <<dataclass>>
1539 +path : str
1540 +conflict_type : str
1541 +ours_summary : str
1542 +theirs_summary : str
1543 +addresses : list~str~
1544 }
1545 class DriftReport {
1546 <<dataclass>>
1547 +has_drift : bool
1548 +summary : str
1549 +delta : StateDelta
1550 }
1551 class MuseDomainPlugin {
1552 <<Protocol runtime_checkable>>
1553 +snapshot(live_state) StateSnapshot
1554 +diff(base, target) StateDelta
1555 +merge(base, left, right, *, repo_root) MergeResult
1556 +drift(committed, live) DriftReport
1557 +apply(delta, live_state) LiveState
1558 +schema() DomainSchema
1559 }
1560 class StructuredMergePlugin {
1561 <<Protocol extends MuseDomainPlugin>>
1562 +merge_ops(base, ours, theirs, ours_ops, theirs_ops) MergeResult
1563 }
1564 class CRDTPlugin {
1565 <<Protocol extends MuseDomainPlugin>>
1566 +crdt_schema() list~CRDTDimensionSpec~
1567 +join(a, b) CRDTSnapshotManifest
1568 +to_crdt_state(snapshot) CRDTSnapshotManifest
1569 +from_crdt_state(crdt) StateSnapshot
1570 }
1571 class MidiPlugin {
1572 <<MuseDomainPlugin implementation>>
1573 +snapshot(live_state) StateSnapshot
1574 +diff(base, target) StateDelta
1575 +merge(base, left, right, *, repo_root) MergeResult
1576 +drift(committed, live) DriftReport
1577 +apply(delta, live_state) LiveState
1578 +schema() DomainSchema
1579 }
1580
1581 MuseDomainPlugin ..> SnapshotManifest : StateSnapshot
1582 MuseDomainPlugin ..> StructuredDelta : StateDelta
1583 MuseDomainPlugin --> MergeResult : merge returns
1584 MuseDomainPlugin --> DriftReport : drift returns
1585 StructuredMergePlugin --|> MuseDomainPlugin
1586 CRDTPlugin --|> MuseDomainPlugin
1587 MidiPlugin ..|> MuseDomainPlugin : implements
1588 MergeResult --> SnapshotManifest : merged
1589 MergeResult *-- ConflictRecord : conflict_records
1590 DriftReport --> StructuredDelta : delta
1591 ```
1592
1593 ---
1594
1595 ### Diagram 2 — DomainOp Discriminated Union
1596
1597 The typed operation algebra. `PatchOp` is recursive via `child_ops`.
1598 `MutateOp` carries per-field deltas via `FieldMutation`.
1599
1600 ```mermaid
1601 classDiagram
1602 class DomainOp {
1603 <<type alias>>
1604 LeafDomainOp | PatchOp
1605 }
1606 class InsertOp {
1607 <<TypedDict op=insert>>
1608 +address : DomainAddress
1609 +position : int | None
1610 +content_id : str
1611 +content_summary : str
1612 }
1613 class DeleteOp {
1614 <<TypedDict op=delete>>
1615 +address : DomainAddress
1616 +position : int | None
1617 +content_id : str
1618 +content_summary : str
1619 }
1620 class MoveOp {
1621 <<TypedDict op=move>>
1622 +address : DomainAddress
1623 +from_position : int
1624 +to_position : int
1625 +content_id : str
1626 }
1627 class ReplaceOp {
1628 <<TypedDict op=replace>>
1629 +address : DomainAddress
1630 +position : int | None
1631 +old_content_id : str
1632 +new_content_id : str
1633 +old_summary : str
1634 +new_summary : str
1635 }
1636 class FieldMutation {
1637 <<TypedDict>>
1638 +old : str
1639 +new : str
1640 }
1641 class MutateOp {
1642 <<TypedDict op=mutate>>
1643 +address : DomainAddress
1644 +entity_id : str
1645 +old_content_id : str
1646 +new_content_id : str
1647 +fields : dict~str, FieldMutation~
1648 +old_summary : str
1649 +new_summary : str
1650 }
1651 class PatchOp {
1652 <<TypedDict op=patch>>
1653 +address : DomainAddress
1654 +child_ops : list~DomainOp~
1655 +child_domain : str
1656 +child_summary : str
1657 }
1658
1659 DomainOp ..> InsertOp : variant
1660 DomainOp ..> DeleteOp : variant
1661 DomainOp ..> MoveOp : variant
1662 DomainOp ..> ReplaceOp : variant
1663 DomainOp ..> MutateOp : variant
1664 DomainOp ..> PatchOp : variant
1665 MutateOp *-- FieldMutation : fields values
1666 PatchOp *-- DomainOp : child_ops (recursive)
1667 ```
1668
1669 ---
1670
1671 ### Diagram 3 — Domain Schema and Diff Input Pairing
1672
1673 Each `ElementSchema` variant is paired with a `DiffInput` variant sharing
1674 the same `kind` discriminator. `diff_by_schema()` uses this to dispatch to
1675 the correct algorithm.
1676
1677 ```mermaid
1678 classDiagram
1679 class ElementSchema {
1680 <<type alias>>
1681 SequenceSchema | TreeSchema | TensorSchema | SetSchema | MapSchema
1682 }
1683 class SequenceSchema {
1684 <<TypedDict kind=sequence>>
1685 +element_type : str
1686 +identity : by_id | by_position | by_content
1687 +diff_algorithm : lcs | myers | patience
1688 +alphabet : list~str~ | None
1689 }
1690 class TreeSchema {
1691 <<TypedDict kind=tree>>
1692 +node_type : str
1693 +diff_algorithm : zhang_shasha | gumtree
1694 }
1695 class TensorSchema {
1696 <<TypedDict kind=tensor>>
1697 +dtype : float32 | float64 | int8 | ...
1698 +rank : int
1699 +epsilon : float
1700 +diff_mode : sparse | block | full
1701 }
1702 class SetSchema {
1703 <<TypedDict kind=set>>
1704 +element_type : str
1705 +identity : by_content | by_id
1706 }
1707 class MapSchema {
1708 <<TypedDict kind=map>>
1709 +key_type : str
1710 +value_schema : ElementSchema
1711 +identity : by_key
1712 }
1713 class SequenceInput {
1714 <<TypedDict kind=sequence>>
1715 +items : list~str~
1716 }
1717 class TreeInput {
1718 <<TypedDict kind=tree>>
1719 +root : TreeNode
1720 }
1721 class TensorInput {
1722 <<TypedDict kind=tensor>>
1723 +values : list~float~
1724 }
1725 class SetInput {
1726 <<TypedDict kind=set>>
1727 +items : frozenset~str~
1728 }
1729 class MapInput {
1730 <<TypedDict kind=map>>
1731 +entries : dict~str, str~
1732 }
1733
1734 ElementSchema ..> SequenceSchema : variant
1735 ElementSchema ..> TreeSchema : variant
1736 ElementSchema ..> TensorSchema : variant
1737 ElementSchema ..> SetSchema : variant
1738 ElementSchema ..> MapSchema : variant
1739 MapSchema --> ElementSchema : value_schema recursive
1740 SequenceSchema ..> SequenceInput : paired input
1741 TreeSchema ..> TreeInput : paired input
1742 TensorSchema ..> TensorInput : paired input
1743 SetSchema ..> SetInput : paired input
1744 MapSchema ..> MapInput : paired input
1745 ```
1746
1747 ---
1748
1749 ### Diagram 4 — CRDT Primitive Lattice
1750
1751 Each CRDT has a class with `join()` satisfying lattice laws and a wire-format
1752 TypedDict for serialisation. All are used by `CRDTPlugin` and the CRDT annotation
1753 fields in `CommitRecord`.
1754
1755 ```mermaid
1756 classDiagram
1757 class VectorClock {
1758 <<class CRDT>>
1759 +increment(agent_id) VectorClock
1760 +merge(other) VectorClock
1761 +happens_before(other) bool
1762 +concurrent_with(other) bool
1763 +to_dict() dict~str, int~
1764 }
1765 class LWWValue {
1766 <<TypedDict wire>>
1767 +value : str
1768 +timestamp : float
1769 +author : str
1770 }
1771 class LWWRegister {
1772 <<class CRDT>>
1773 +read() str
1774 +write(value, ts, author) LWWRegister
1775 +join(other) LWWRegister
1776 +to_dict() LWWValue
1777 }
1778 class ORSetEntry {
1779 <<TypedDict>>
1780 +element : str
1781 +token : str
1782 }
1783 class ORSetDict {
1784 <<TypedDict wire>>
1785 +entries : list~ORSetEntry~
1786 +tombstones : list~str~
1787 }
1788 class ORSet {
1789 <<class CRDT add-wins>>
1790 +add(element) tuple~ORSet, str~
1791 +remove(element, tokens) ORSet
1792 +join(other) ORSet
1793 +elements() frozenset~str~
1794 +to_dict() ORSetDict
1795 }
1796 class RGAElement {
1797 <<TypedDict>>
1798 +id : str
1799 +value : str
1800 +deleted : bool
1801 +parent_id : str | None
1802 }
1803 class RGA {
1804 <<class CRDT sequence>>
1805 +insert(after_id, value, element_id) RGA
1806 +delete(element_id) RGA
1807 +join(other) RGA
1808 +to_sequence() list~str~
1809 +to_dict() list~RGAElement~
1810 }
1811 class AWMapEntry {
1812 <<TypedDict>>
1813 +key : str
1814 +value : str
1815 +token : str
1816 }
1817 class AWMapDict {
1818 <<TypedDict wire>>
1819 +entries : list~AWMapEntry~
1820 +tombstones : list~str~
1821 }
1822 class AWMap {
1823 <<class CRDT add-wins map>>
1824 +set(key, value) AWMap
1825 +remove(key) AWMap
1826 +join(other) AWMap
1827 +get(key) str | None
1828 +to_dict() AWMapDict
1829 }
1830 class GCounter {
1831 <<class CRDT g-counter>>
1832 +increment(agent_id, by) GCounter
1833 +join(other) GCounter
1834 +value() int
1835 +to_dict() dict~str, int~
1836 }
1837
1838 LWWRegister ..> LWWValue : to_dict
1839 ORSet ..> ORSetDict : to_dict
1840 ORSetDict *-- ORSetEntry : entries
1841 RGA ..> RGAElement : to_dict items
1842 AWMap ..> AWMapDict : to_dict
1843 AWMapDict *-- AWMapEntry : entries
1844 ```
1845
1846 ---
1847
1848 ### Diagram 5 — Store Wire-Format and In-Memory Dataclasses
1849
1850 The two-layer design: wire-format `TypedDict`s for JSON serialisation, rich
1851 `dataclass`es for in-memory logic. `CommitRecord` now carries agent provenance
1852 and CRDT annotation fields.
1853
1854 ```mermaid
1855 classDiagram
1856 class CommitDict {
1857 <<TypedDict total=False wire>>
1858 +commit_id : str
1859 +repo_id : str
1860 +branch : str
1861 +snapshot_id : str
1862 +message : str
1863 +committed_at : str
1864 +parent_commit_id : str | None
1865 +parent2_commit_id : str | None
1866 +structured_delta : StructuredDelta | None
1867 +sem_ver_bump : SemVerBump
1868 +agent_id : str
1869 +model_id : str
1870 +format_version : int
1871 +reviewed_by : list~str~
1872 +test_runs : int
1873 }
1874 class CommitRecord {
1875 <<dataclass>>
1876 +commit_id : str
1877 +repo_id : str
1878 +branch : str
1879 +snapshot_id : str
1880 +message : str
1881 +committed_at : datetime
1882 +parent_commit_id : str | None
1883 +parent2_commit_id : str | None
1884 +structured_delta : StructuredDelta | None
1885 +sem_ver_bump : SemVerBump
1886 +agent_id : str
1887 +model_id : str
1888 +format_version : int
1889 +reviewed_by : list~str~
1890 +test_runs : int
1891 +to_dict() CommitDict
1892 +from_dict(d) CommitRecord
1893 }
1894 class SnapshotDict {
1895 <<TypedDict wire>>
1896 +snapshot_id : str
1897 +manifest : dict~str, str~
1898 +created_at : str
1899 }
1900 class SnapshotRecord {
1901 <<dataclass>>
1902 +snapshot_id : str
1903 +manifest : dict~str, str~
1904 +created_at : datetime
1905 +to_dict() SnapshotDict
1906 +from_dict(d) SnapshotRecord
1907 }
1908 class TagDict {
1909 <<TypedDict wire>>
1910 +tag_id : str
1911 +repo_id : str
1912 +commit_id : str
1913 +tag : str
1914 +created_at : str
1915 }
1916 class TagRecord {
1917 <<dataclass>>
1918 +tag_id : str
1919 +repo_id : str
1920 +commit_id : str
1921 +tag : str
1922 +created_at : datetime
1923 +to_dict() TagDict
1924 +from_dict(d) TagRecord
1925 }
1926 class RemoteCommitPayload {
1927 <<TypedDict total=False>>
1928 +commit_id : str
1929 +snapshot_id : str
1930 +manifest : dict~str, str~
1931 }
1932
1933 CommitRecord ..> CommitDict : to_dict / from_dict
1934 SnapshotRecord ..> SnapshotDict : to_dict / from_dict
1935 TagRecord ..> TagDict : to_dict / from_dict
1936 RemoteCommitPayload ..> CommitDict : store_pulled_commit builds
1937 CommitRecord --> SnapshotRecord : snapshot_id reference
1938 ```
1939
1940 ---
1941
1942 ### Diagram 6 — Op Log Types
1943
1944 The op log records every domain operation in causal order. `OpEntry` carries a
1945 `DomainOp`; `OpLog` accumulates entries and can fold them into a
1946 `StructuredDelta`.
1947
1948 ```mermaid
1949 classDiagram
1950 class OpEntry {
1951 <<TypedDict>>
1952 +op_id : str
1953 +actor_id : str
1954 +lamport_ts : int
1955 +parent_op_ids : list~str~
1956 +domain : str
1957 +domain_op : DomainOp
1958 +created_at : str
1959 +intent_id : str
1960 +reservation_id : str
1961 }
1962 class OpLogCheckpoint {
1963 <<TypedDict>>
1964 +session_id : str
1965 +snapshot_id : str
1966 +lamport_ts : int
1967 +op_count : int
1968 +created_at : str
1969 }
1970 class OpLog {
1971 <<class>>
1972 +append(entry) None
1973 +read_all() list~OpEntry~
1974 +replay_since_checkpoint() list~OpEntry~
1975 +to_structured_delta(domain) StructuredDelta
1976 +checkpoint(snapshot_id) OpLogCheckpoint
1977 +read_checkpoint() OpLogCheckpoint | None
1978 }
1979
1980 OpLog *-- OpEntry : appends
1981 OpLog --> OpLogCheckpoint : checkpoint produces
1982 OpEntry --> OpLogCheckpoint : lamport_ts tracks progress
1983 ```
1984
1985 ---
1986
1987 ### Diagram 7 — Merge Engine State
1988
1989 The in-progress merge state written to disk on conflict and loaded on
1990 continuation.
1991
1992 ```mermaid
1993 classDiagram
1994 class MergeStatePayload {
1995 <<TypedDict total=False>>
1996 +base_commit : str
1997 +ours_commit : str
1998 +theirs_commit : str
1999 +conflict_paths : list~str~
2000 +other_branch : str
2001 }
2002 class MergeState {
2003 <<dataclass frozen>>
2004 +conflict_paths : list~str~
2005 +base_commit : str | None
2006 +ours_commit : str | None
2007 +theirs_commit : str | None
2008 +other_branch : str | None
2009 }
2010 class MergeOpsResult {
2011 <<dataclass>>
2012 +merged_ops : list~DomainOp~
2013 +conflict_ops : list~tuple~DomainOp, DomainOp~~
2014 +is_clean : bool
2015 }
2016
2017 MergeStatePayload ..> MergeState : read_merge_state produces
2018 MergeState ..> MergeStatePayload : write_merge_state serialises
2019 ```
2020
2021 ---
2022
2023 ### Diagram 8 — Attributes and MIDI Dimension Merge
2024
2025 The `.museattributes` rule pipeline flowing into the 21-dimension MIDI merge
2026 engine.
2027
2028 ```mermaid
2029 classDiagram
2030 class AttributeRule {
2031 <<dataclass frozen>>
2032 +path_pattern : str
2033 +dimension : str
2034 +strategy : str
2035 +source_index : int
2036 }
2037 class DimensionSlice {
2038 <<dataclass>>
2039 +name : str
2040 +events : list~tuple~int, Message~~
2041 +content_hash : str
2042 }
2043 class MidiDimensions {
2044 <<dataclass 21 dims>>
2045 +ticks_per_beat : int
2046 +file_type : int
2047 +slices : dict~str, DimensionSlice~
2048 +get(dim) DimensionSlice
2049 }
2050 class MergeResult {
2051 <<dataclass>>
2052 +merged : StateSnapshot
2053 +conflicts : list~str~
2054 +applied_strategies : dict~str, str~
2055 +dimension_reports : dict~str, dict~str, str~~
2056 }
2057 class MidiPlugin {
2058 <<MuseDomainPlugin>>
2059 +merge(base, left, right, *, repo_root) MergeResult
2060 }
2061
2062 MidiPlugin ..> AttributeRule : load_attributes
2063 MidiPlugin ..> MidiDimensions : extract_dimensions
2064 MidiPlugin --> MergeResult : returns
2065 MidiDimensions *-- DimensionSlice : slices (21 buckets)
2066 AttributeRule ..> DimensionSlice : resolve_strategy selects winner
2067 MergeResult --> AttributeRule : applied_strategies reflects rules
2068 ```
2069
2070 ---
2071
2072 ### Diagram 9 — Code Plugin Types
2073
2074 Symbol extraction pipeline for the code domain. `LanguageAdapter` is the
2075 per-language abstraction; `PythonAdapter` and `TreeSitterAdapter` are the
2076 concrete implementations.
2077
2078 ```mermaid
2079 classDiagram
2080 class LanguageAdapter {
2081 <<Protocol runtime_checkable>>
2082 +supported_extensions() frozenset~str~
2083 +parse_symbols(source, file_path) SymbolTree
2084 +file_content_id(source) str
2085 }
2086 class LangSpec {
2087 <<TypedDict tree-sitter config>>
2088 +extensions : frozenset~str~
2089 +module_name : str
2090 +lang_func : str
2091 +query_str : str
2092 +kind_map : dict~str, SymbolKind~
2093 }
2094 class SymbolRecord {
2095 <<TypedDict>>
2096 +kind : SymbolKind
2097 +name : str
2098 +qualified_name : str
2099 +content_id : str
2100 +body_hash : str
2101 +signature_id : str
2102 +metadata_id : str
2103 +canonical_key : str
2104 +lineno : int
2105 +end_lineno : int
2106 }
2107 class PythonAdapter {
2108 <<LanguageAdapter>>
2109 ast.parse / ast.unparse
2110 }
2111 class TreeSitterAdapter {
2112 <<LanguageAdapter>>
2113 tree-sitter CST, error-tolerant
2114 }
2115 class FallbackAdapter {
2116 <<LanguageAdapter>>
2117 empty SymbolTree, SHA-256 only
2118 }
2119
2120 LanguageAdapter ..> SymbolRecord : parse_symbols returns dict
2121 PythonAdapter ..|> LanguageAdapter
2122 TreeSitterAdapter ..|> LanguageAdapter
2123 FallbackAdapter ..|> LanguageAdapter
2124 TreeSitterAdapter --> LangSpec : configured by
2125 ```
2126
2127 ---
2128
2129 ### Diagram 10 — Configuration, Import, Stash, and Errors
2130
2131 Supporting types for CLI infrastructure.
2132
2133 ```mermaid
2134 classDiagram
2135 class MuseConfig {
2136 <<TypedDict total=False>>
2137 +auth : AuthEntry
2138 +remotes : dict~str, RemoteEntry~
2139 +domain : dict~str, str~
2140 }
2141 class AuthEntry {
2142 <<TypedDict total=False>>
2143 +token : str
2144 }
2145 class RemoteEntry {
2146 <<TypedDict total=False>>
2147 +url : str
2148 +branch : str
2149 }
2150 class RemoteConfig {
2151 <<TypedDict public API>>
2152 +name : str
2153 +url : str
2154 }
2155 class StashEntry {
2156 <<TypedDict>>
2157 +snapshot_id : str
2158 +manifest : dict~str, str~
2159 +branch : str
2160 +stashed_at : str
2161 }
2162 class MuseImportData {
2163 <<dataclass>>
2164 +source_path : Path
2165 +format : str
2166 +notes : list~NoteEvent~
2167 +raw_meta : RawMeta
2168 }
2169 class NoteEvent {
2170 <<dataclass>>
2171 +pitch : int
2172 +velocity : int
2173 +start_tick : int
2174 +duration_ticks : int
2175 }
2176 class ExitCode {
2177 <<IntEnum>>
2178 SUCCESS = 0
2179 USER_ERROR = 1
2180 REPO_NOT_FOUND = 2
2181 INTERNAL_ERROR = 3
2182 }
2183 class MuseCLIError {
2184 <<Exception>>
2185 +exit_code : ExitCode
2186 }
2187 class RepoNotFoundError {
2188 <<MuseCLIError>>
2189 default REPO_NOT_FOUND
2190 }
2191
2192 MuseConfig *-- AuthEntry : auth
2193 MuseConfig *-- RemoteEntry : remotes
2194 RemoteEntry ..> RemoteConfig : list_remotes projects to
2195 MuseImportData *-- NoteEvent : notes
2196 RepoNotFoundError --|> MuseCLIError
2197 MuseCLIError --> ExitCode : exit_code
2198 ```
2199
2200 ---
2201
2202 ### Diagram 11 — Full Entity Dependency Overview
2203
2204 All named entities grouped by layer, showing the dependency flow from domain
2205 protocol down through diff algorithms, CRDTs, store, and plugins.
2206
2207 ```mermaid
2208 classDiagram
2209 class MuseDomainPlugin {
2210 <<Protocol>>
2211 snapshot · diff · merge · drift · apply · schema
2212 }
2213 class StructuredDelta {
2214 <<TypedDict StateDelta>>
2215 domain · ops: list~DomainOp~ · sem_ver_bump
2216 }
2217 class SnapshotManifest {
2218 <<TypedDict StateSnapshot>>
2219 files: dict~str,str~ · domain
2220 }
2221 class MergeResult {
2222 <<dataclass>>
2223 merged · conflicts · op_log · conflict_records
2224 }
2225 class DomainOp {
2226 <<type alias>>
2227 Insert | Delete | Move | Replace | Mutate | Patch
2228 }
2229 class DomainSchema {
2230 <<TypedDict>>
2231 dimensions: list~DimensionSpec~ · top_level: ElementSchema
2232 }
2233 class CommitRecord {
2234 <<dataclass>>
2235 commit_id · structured_delta · sem_ver_bump · reviewed_by · test_runs
2236 }
2237 class SnapshotRecord {
2238 <<dataclass>>
2239 snapshot_id · manifest: dict~str,str~
2240 }
2241 class MergeState {
2242 <<dataclass frozen>>
2243 conflict_paths · base/ours/theirs commits
2244 }
2245 class OpEntry {
2246 <<TypedDict>>
2247 op_id · domain_op: DomainOp · lamport_ts
2248 }
2249 class GCounter {
2250 <<CRDT>>
2251 increment · join · value
2252 }
2253 class ORSet {
2254 <<CRDT add-wins>>
2255 add · remove · join · elements
2256 }
2257 class AttributeRule {
2258 <<dataclass frozen>>
2259 path_pattern · dimension · strategy
2260 }
2261 class MidiDimensions {
2262 <<dataclass>>
2263 slices: dict~str,DimensionSlice~
2264 }
2265 class SymbolRecord {
2266 <<TypedDict>>
2267 kind · content_id · body_hash · signature_id
2268 }
2269
2270 MuseDomainPlugin ..> SnapshotManifest : StateSnapshot
2271 MuseDomainPlugin ..> StructuredDelta : StateDelta
2272 MuseDomainPlugin --> MergeResult : merge
2273 MuseDomainPlugin --> DomainSchema : schema
2274 StructuredDelta *-- DomainOp : ops
2275 OpEntry --> DomainOp : domain_op
2276 CommitRecord --> SnapshotRecord : snapshot_id
2277 CommitRecord --> StructuredDelta : structured_delta
2278 CommitRecord --> ORSet : reviewed_by uses
2279 CommitRecord --> GCounter : test_runs uses
2280 MergeState ..> CommitRecord : references commits
2281 AttributeRule ..> MidiDimensions : resolve_strategy selects slices
2282 SymbolRecord ..> DomainOp : drives code plugin ops
2283 ```