cgcardona / muse public
domain.py python
178 lines 6.3 KB
5f1a074d feat: implement .museignore — gitignore-style snapshot exclusion (#7) Gabriel Cardona <cgcardona@gmail.com> 3d ago
1 """MuseDomainPlugin — the five-interface protocol that defines a Muse domain.
2
3 Muse provides the DAG engine, content-addressed object store, branching,
4 lineage walking, topological log graph, and merge base finder. A domain plugin
5 implements these five interfaces and Muse does the rest.
6
7 The music plugin (``muse.plugins.music``) is the reference implementation.
8 Every other domain — scientific simulation, genomics, 3D spatial design,
9 spacetime — is a new plugin.
10 """
11 from __future__ import annotations
12
13 import pathlib
14 from dataclasses import dataclass, field
15 from typing import Protocol, TypedDict, runtime_checkable
16
17
18 # ---------------------------------------------------------------------------
19 # Named snapshot and delta types
20 # ---------------------------------------------------------------------------
21
22
23 class SnapshotManifest(TypedDict):
24 """Content-addressed snapshot of domain state.
25
26 ``files`` maps workspace-relative POSIX paths to their SHA-256 content
27 digests. ``domain`` identifies which plugin produced this snapshot.
28 """
29
30 files: dict[str, str]
31 domain: str
32
33
34 class DeltaManifest(TypedDict):
35 """Minimal change description between two snapshots.
36
37 Each list contains workspace-relative POSIX paths. ``domain`` identifies
38 the plugin that produced this delta.
39 """
40
41 domain: str
42 added: list[str]
43 removed: list[str]
44 modified: list[str]
45
46
47 # ---------------------------------------------------------------------------
48 # Type aliases used in the protocol signatures
49 # ---------------------------------------------------------------------------
50
51 #: Live state is either an already-snapshotted manifest dict or a workdir path.
52 #: The music plugin accepts both: a Path (for CLI commit/status) and a
53 #: SnapshotManifest dict (for in-memory merge and diff operations).
54 LiveState = SnapshotManifest | pathlib.Path
55
56 #: A content-addressed, immutable snapshot of state at a point in time.
57 StateSnapshot = SnapshotManifest
58
59 #: The minimal change between two snapshots — additions, removals, mutations.
60 StateDelta = DeltaManifest
61
62
63 # ---------------------------------------------------------------------------
64 # Merge and drift result types
65 # ---------------------------------------------------------------------------
66
67
68 @dataclass
69 class MergeResult:
70 """Outcome of a three-way merge between two divergent state lines.
71
72 ``merged`` is the reconciled snapshot. ``conflicts`` is a list of
73 workspace-relative file paths that could not be auto-merged and require
74 manual resolution. An empty ``conflicts`` list means the merge was clean.
75 The CLI is responsible for formatting user-facing messages from these paths.
76 """
77
78 merged: StateSnapshot
79 conflicts: list[str] = field(default_factory=list)
80
81 @property
82 def is_clean(self) -> bool:
83 return len(self.conflicts) == 0
84
85
86 @dataclass
87 class DriftReport:
88 """Gap between committed state and current live state.
89
90 ``has_drift`` is ``True`` when the live state differs from the committed
91 snapshot. ``summary`` is a human-readable description of what changed.
92 ``delta`` is the machine-readable diff for programmatic consumers.
93 """
94
95 has_drift: bool
96 summary: str = ""
97 delta: StateDelta = field(default_factory=lambda: DeltaManifest(
98 domain="", added=[], removed=[], modified=[],
99 ))
100
101
102 # ---------------------------------------------------------------------------
103 # The plugin protocol
104 # ---------------------------------------------------------------------------
105
106
107 @runtime_checkable
108 class MuseDomainPlugin(Protocol):
109 """The five interfaces a domain plugin must implement.
110
111 Muse provides everything else: the DAG, branching, checkout, lineage
112 walking, ASCII log graph, and merge base finder. Implement these five
113 methods and your domain gets the full Muse VCS for free.
114
115 Music is the reference implementation (``muse.plugins.music``).
116 """
117
118 def snapshot(self, live_state: LiveState) -> StateSnapshot:
119 """Capture current live state as a serialisable, hashable snapshot.
120
121 The returned ``SnapshotManifest`` must be JSON-serialisable. Muse will
122 compute a SHA-256 content address from the canonical JSON form and
123 store the snapshot as a blob in ``.muse/objects/``.
124
125 **``.museignore`` contract** — when *live_state* is a
126 ``pathlib.Path`` (the ``muse-work/`` directory), domain plugin
127 implementations **must** honour ``.museignore`` by calling
128 :func:`muse.core.ignore.load_patterns` on the repository root and
129 filtering out paths matched by :func:`muse.core.ignore.is_ignored`.
130 This ensures that OS artifacts, build outputs, and domain-specific
131 scratch files are never committed, regardless of which plugin is active.
132 See ``docs/reference/museignore.md`` for the full format reference.
133 """
134 ...
135
136 def diff(self, base: StateSnapshot, target: StateSnapshot) -> StateDelta:
137 """Compute the minimal delta between two snapshots.
138
139 Returns a ``DeltaManifest`` listing which paths were added, removed,
140 or modified. Muse stores deltas alongside commits so that ``muse show``
141 can display a human-readable summary without reloading full blobs.
142 """
143 ...
144
145 def merge(
146 self,
147 base: StateSnapshot,
148 left: StateSnapshot,
149 right: StateSnapshot,
150 ) -> MergeResult:
151 """Three-way merge two divergent state lines against a common base.
152
153 ``base`` is the common ancestor (merge base). ``left`` and ``right``
154 are the two divergent snapshots. Returns a ``MergeResult`` with the
155 reconciled snapshot and any unresolvable conflicts.
156 """
157 ...
158
159 def drift(
160 self,
161 committed: StateSnapshot,
162 live: LiveState,
163 ) -> DriftReport:
164 """Compare committed state against current live state.
165
166 Used by ``muse status`` to detect uncommitted changes. Returns a
167 ``DriftReport`` describing whether the live state has diverged from
168 the last committed snapshot and, if so, by how much.
169 """
170 ...
171
172 def apply(self, delta: StateDelta, live_state: LiveState) -> LiveState:
173 """Apply a delta to produce a new live state.
174
175 Used by ``muse checkout`` to reconstruct a historical state. Applies
176 ``delta`` on top of ``live_state`` and returns the resulting state.
177 """
178 ...