#!/usr/bin/env python3 """Muse Domain Registry — standalone HTML generator. Produces a self-contained, shareable page that explains the MuseDomainPlugin protocol, shows the registered plugin ecosystem, and guides developers through scaffolding and publishing their own domain plugin. Stand-alone usage ----------------- python tools/render_domain_registry.py python tools/render_domain_registry.py --out artifacts/domain_registry.html """ from __future__ import annotations import json import pathlib import subprocess import sys _ROOT = pathlib.Path(__file__).resolve().parent.parent # --------------------------------------------------------------------------- # Live domain data from the CLI # --------------------------------------------------------------------------- def _compute_crdt_demos() -> list[dict]: """Run the four CRDT primitives live and return formatted demo output.""" sys.path.insert(0, str(_ROOT)) try: from muse.core.crdts import GCounter, LWWRegister, ORSet, VectorClock # ORSet base, _ = ORSet().add("annotation-GO:0001234") a, _ = base.add("annotation-GO:0001234") b = base.remove("annotation-GO:0001234", base.tokens_for("annotation-GO:0001234")) merged = a.join(b) orset_out = "\n".join([ "ORSet — add-wins concurrent merge:", f" base elements: {sorted(base.elements())}", f" A re-adds → elements: {sorted(a.elements())}", f" B removes → elements: {sorted(b.elements())}", f" join(A, B) → elements: {sorted(merged.elements())}", " [A's new token is not tombstoned — add always wins]", ]) # LWWRegister ra = LWWRegister.from_dict({"value": "80 BPM", "timestamp": 1.0, "author": "agent-A"}) rb = LWWRegister.from_dict({"value": "120 BPM", "timestamp": 2.0, "author": "agent-B"}) rm = ra.join(rb) lww_out = "\n".join([ "LWWRegister — last-write-wins scalar:", f" Agent A writes: '{ra.read()}' at t=1.0", f" Agent B writes: '{rb.read()}' at t=2.0 (later)", f" join(A, B) → '{rm.read()}' [higher timestamp wins]", " join(B, A) → same result [commutativity]", ]) # GCounter ca = GCounter().increment("agent-A").increment("agent-A") cb = GCounter().increment("agent-B").increment("agent-B").increment("agent-B") cm = ca.join(cb) gc_out = "\n".join([ "GCounter — grow-only distributed counter:", f" Agent A x2 → A slot: {ca.value_for('agent-A')}", f" Agent B x3 → B slot: {cb.value_for('agent-B')}", f" join(A, B) global value: {cm.value()}", " [monotonically non-decreasing — joins never lose counts]", ]) # VectorClock va = VectorClock().increment("agent-A") vb = VectorClock().increment("agent-B") vm = va.merge(vb) vc_out = "\n".join([ "VectorClock — causal ordering:", f" Agent A: {va.to_dict()}", f" Agent B: {vb.to_dict()}", f" concurrent_with(A, B): {va.concurrent_with(vb)}", f" merge(A, B): {vm.to_dict()} [component-wise max]", ]) return [ {"type": "ORSet", "sub": "Observed-Remove Set", "color": "#bc8cff", "icon": "∪", "output": orset_out}, {"type": "LWWRegister", "sub": "Last-Write-Wins Register", "color": "#58a6ff", "icon": "✎", "output": lww_out}, {"type": "GCounter", "sub": "Grow-Only Distributed Counter", "color": "#3fb950", "icon": "↑", "output": gc_out}, {"type": "VectorClock", "sub": "Causal Ordering", "color": "#f9a825", "icon": "⊕", "output": vc_out}, ] except Exception as exc: print(f" ⚠ CRDT demo failed ({exc}); using static fallback") return [] def _load_domains() -> list[dict]: """Run `muse domains --json` and return parsed output.""" try: result = subprocess.run( [sys.executable, "-m", "muse", "domains", "--json"], capture_output=True, text=True, cwd=str(_ROOT), timeout=15, ) if result.returncode == 0: raw = result.stdout.strip() data: list[dict] = json.loads(raw) return data except Exception: pass # Fallback: static reference data return [ { "domain": "music", "active": "true", "capabilities": ["Typed Deltas", "Domain Schema", "OT Merge"], "schema": { "schema_version": "1", "merge_mode": "three_way", "description": "MIDI and audio file versioning with note-level diff and semantic merge", "dimensions": [ {"name": "melodic", "description": "Note pitches and durations over time"}, {"name": "harmonic", "description": "Chord progressions and key signatures"}, {"name": "dynamic", "description": "Velocity and expression curves"}, {"name": "structural", "description": "Track layout, time signatures, tempo map"}, ], }, } ] # --------------------------------------------------------------------------- # Scaffold template (shown in the "Build in 3 steps" section) # --------------------------------------------------------------------------- _TYPED_DELTA_EXAMPLE = """\ # muse show --json (any commit, any domain) { "commit_id": "b26f3c99", "message": "Resolve: integrate shared-state (A+B reconciled)", "operations": [ { "op_type": "ReplaceOp", "address": "shared-state.mid", "before_hash": "a1b2c3d4", "after_hash": "e5f6g7h8", "dimensions": ["structural"] }, { "op_type": "InsertOp", "address": "beta-a.mid", "after_hash": "09ab1234", "dimensions": ["rhythmic", "dynamic"] } ], "summary": { "inserted": 1, "replaced": 1, "deleted": 0 } }""" _OT_MERGE_EXAMPLE = """\ # Scenario A — independent InsertOps at different addresses → commute → clean merge left: InsertOp("ot-notes-a.mid") # tick=0, C4 E4 G4 right: InsertOp("ot-notes-b.mid") # tick=480, D4 F4 A4 transform(left, right) → no overlap → both applied result: both files present, zero conflicts ✓ # Scenario B — same address, different content → genuine conflict base: shared-melody.mid # C4 G4 left: ReplaceOp("shared-melody.mid") # C4 E4 G4 (major triad) right: ReplaceOp("shared-melody.mid") # C4 Eb4 G4 (minor triad) transform(left, right) → same address, non-commuting content result: ❌ Merge conflict in 1 file(s): CONFLICT (both modified): shared-melody.mid [musical intent differs — human must choose major or minor]""" _SCAFFOLD_SNIPPET = """\ from __future__ import annotations from muse.domain import ( MuseDomainPlugin, LiveState, StateSnapshot, StateDelta, DriftReport, MergeResult, DomainSchema, ) class GenomicsPlugin(MuseDomainPlugin): \"\"\"Version control for genomic sequences.\"\"\" def snapshot(self, live_state: LiveState) -> StateSnapshot: # Serialize current genome state to a content-addressable blob raise NotImplementedError def diff(self, base: StateSnapshot, target: StateSnapshot) -> StateDelta: # Compute minimal delta between two snapshots raise NotImplementedError def merge(self, base: StateSnapshot, left: StateSnapshot, right: StateSnapshot) -> MergeResult: # Three-way merge — surface conflicts per dimension raise NotImplementedError def drift(self, committed: StateSnapshot, live: LiveState) -> DriftReport: # Detect uncommitted changes in the working state raise NotImplementedError def apply(self, delta: StateDelta, live_state: LiveState) -> LiveState: # Reconstruct historical state from a delta raise NotImplementedError def schema(self) -> DomainSchema: # Declare dimensions — drives diff algorithm selection raise NotImplementedError """ # --------------------------------------------------------------------------- # Planned / aspirational domains # --------------------------------------------------------------------------- _PLANNED_DOMAINS = [ { "name": "Genomics", "icon": "🧬", "status": "planned", "tagline": "Version sequences, variants, and annotations", "dimensions": ["sequence", "variants", "annotations", "metadata"], "color": "#3fb950", }, { "name": "3D / Spatial", "icon": "🌐", "status": "planned", "tagline": "Merge spatial fields, meshes, and simulation frames", "dimensions": ["geometry", "materials", "physics", "temporal"], "color": "#58a6ff", }, { "name": "Financial", "icon": "📈", "status": "planned", "tagline": "Track model versions, alpha signals, and risk state", "dimensions": ["signals", "positions", "risk", "parameters"], "color": "#f9a825", }, { "name": "Scientific Simulation", "icon": "⚛️", "status": "planned", "tagline": "Snapshot simulation state across timesteps and parameter spaces", "dimensions": ["state", "parameters", "observables", "checkpoints"], "color": "#ab47bc", }, { "name": "Your Domain", "icon": "✦", "status": "yours", "tagline": "Six methods. Any multidimensional state. Full VCS for free.", "dimensions": ["your_dim_1", "your_dim_2", "..."], "color": "#4f8ef7", }, ] # --------------------------------------------------------------------------- # Distribution model description # --------------------------------------------------------------------------- _DISTRIBUTION_LEVELS = [ { "tier": "Local", "icon": "💻", "title": "Local plugin (right now)", "color": "#3fb950", "steps": [ "muse domains --new <name>", "Implement 6 methods in muse/plugins/<name>/plugin.py", "Register in muse/plugins/registry.py", "muse init --domain <name>", ], "desc": "Works today. Scaffold → implement → register. " "Your plugin lives alongside the core.", }, { "tier": "Shareable", "icon": "📦", "title": "pip-installable package (right now)", "color": "#58a6ff", "steps": [ "Package your plugin as a Python module", "pip install git+https://github.com/you/muse-plugin-genomics", "Register the entry-point in pyproject.toml", "muse init --domain genomics", ], "desc": "Share your plugin as a standard Python package. " "Anyone with pip can install and use it.", }, { "tier": "MuseHub", "icon": "🌐", "title": "Centralized registry (coming — MuseHub)", "color": "#bc8cff", "steps": [ "musehub publish muse-plugin-genomics", "musehub search genomics", "muse init --domain @musehub/genomics", "Browse plugins at musehub.io", ], "desc": "MuseHub is a planned centralized registry — npm for Muse plugins. " "Versioned, searchable, one-command install.", }, ] # --------------------------------------------------------------------------- # HTML template # --------------------------------------------------------------------------- def _render_capability_card(cap: dict) -> str: color = cap["color"] return f"""
{cap['output']}
{short_desc}
{s}{d['desc']}
One protocol. Any domain. Six methods between you and a complete version control system — branching, merging, conflict resolution, time-travel, and typed diffs — for free.
Every domain — music, genomics, 3D spatial, financial models — implements the same six-method protocol. The core engine handles everything else: content-addressed storage, DAG, branches, log, merge base, cherry-pick, revert, stash, tags.
The core engine provides four advanced capabilities that any domain plugin can opt into. Implement the protocol — the engine does the rest.
Unlike Git's blob diffs, Muse deltas are typed objects:
InsertOp, ReplaceOp, DeleteOp — each
carrying the address, before/after hashes, and affected dimensions.
Machine-readable with muse show --json.
{{TYPED_DELTA_EXAMPLE}}
Each plugin's schema() method declares its dimensions and merge mode.
The engine uses this to select the right diff algorithm per dimension and to
surface only the dimensions that actually conflict.
Plugins implementing StructuredMergePlugin get operational transformation. Operations at different addresses commute automatically — only operations on the same address with incompatible intent surface a conflict.
{{OT_MERGE_EXAMPLE}}
Plugins implementing CRDTPlugin get four battle-tested convergent data structures. No coordination required between replicas.
One command scaffolds the entire plugin skeleton. You fill in six methods. The full VCS follows.
raise NotImplementedError with your domain's
snapshot, diff, merge, drift, apply, and schema logic.
registry.py, then every Muse command works
for your domain out of the box.
muse domains --new genomics producesA fully typed, immediately runnable plugin skeleton. Every method has the correct signature. You replace the stubs — the protocol does the rest.
Full walkthrough → docs/guide/plugin-authoring-guide.md · CRDT extension → docs/guide/crdt-reference.md
Domains currently registered in this Muse instance. The active domain
is the one used when you run muse commit, muse diff,
and all other commands.
Music is the reference implementation. These are the domains planned next — and the slot waiting for yours.
Three tiers of distribution — from local prototype to globally searchable registry. Start local, publish when ready.
A centralized, searchable registry for Muse domain plugins — think npm or crates.io, but for any multidimensional versioned state. One command to publish. One command to install.
muse init --domain @musehub/genomics