gabriel / musehub public
0002_v2_domains.py python
249 lines 11.9 KB
Raw
sha256:a10adeeb7a0169cb9900f9806ed7a973047258abb6283724fe55e8eb68ff3f0a init: musehub initial commit Human 171 days ago
1 """MuseHub V2 — Domain-agnostic paradigm shift.
2
3 Revision ID: 0002
4 Revises: 0001
5 Create Date: 2026-03-18
6
7 Adds the Muse domain plugin registry and makes all existing tables
8 domain-agnostic.
9
10 Changes:
11 NEW TABLES
12 - musehub_domains: Muse domain plugin registry (@author/slug namespace)
13 - musehub_domain_installs: User ↔ domain adoption tracking
14
15 MUSEHUB_REPOS
16 - ADD domain_id (nullable FK → musehub_domains)
17 - ADD domain_meta JSON (replaces key_signature + tempo_bpm)
18 - DROP key_signature
19 - DROP tempo_bpm
20
21 MUSEHUB_PR_COMMENTS (proposal review comments)
22 - ADD dimension_ref JSON (domain-agnostic replacement for music-specific fields)
23 - DROP target_type
24 - DROP target_track
25 - DROP target_beat_start
26 - DROP target_beat_end
27 - DROP target_note_pitch
28
29 MUSEHUB_ISSUE_COMMENTS
30 - RENAME musical_refs → state_refs
31
32 MUSEHUB_RENDER_JOBS
33 - RENAME midi_count → artifact_count
34 - RENAME mp3_object_ids → audio_object_ids
35 - RENAME image_object_ids → preview_object_ids
36
37 SEED DATA
38 - Insert @gabriel/code built-in domain (symbol-graph code)
39 """
40 from __future__ import annotations
41
42 import json
43 import hashlib
44
45 import sqlalchemy as sa
46 from alembic import op
47
48 revision = "0002"
49 down_revision = "0001"
50 branch_labels = None
51 depends_on = None
52
53
54 def _manifest_hash(capabilities: dict) -> str:
55 """Compute SHA-256 of the capabilities JSON (sorted keys)."""
56 blob = json.dumps(capabilities, sort_keys=True, separators=(",", ":")).encode()
57 return hashlib.sha256(blob).hexdigest()
58
59
60 # Capability manifest for the built-in .code domain
61 _CODE_CAPABILITIES = {
62 "dimensions": [
63 {"name": "symbols", "description": "Function and class symbol graph"},
64 {"name": "hotspots", "description": "Most frequently changed symbols"},
65 {"name": "coupling", "description": "Symbol-level coupling and cohesion"},
66 {"name": "complexity", "description": "Cyclomatic complexity per symbol"},
67 {"name": "churn", "description": "Commit frequency per file and symbol"},
68 {"name": "coverage", "description": "Test coverage by symbol"},
69 {"name": "dependencies", "description": "Import and dependency graph"},
70 {"name": "duplicates", "description": "Semantically duplicated code blocks"},
71 {"name": "refactors", "description": "Detected rename and move operations"},
72 {"name": "types", "description": "Type annotation completeness"},
73 ],
74 "viewer_type": "symbol_graph",
75 "artifact_types": [
76 "text/x-python", "text/typescript", "text/javascript",
77 "text/x-go", "text/x-rust", "text/x-java",
78 ],
79 "merge_semantics": "ot",
80 "supported_commands": [
81 "muse symbols", "muse hotspots", "muse coupling", "muse diff",
82 "muse query", "muse refactor",
83 ],
84 }
85
86
87 def upgrade() -> None:
88 # ── musehub_domains ───────────────────────────────────────────────────────
89 op.create_table(
90 "musehub_domains",
91 sa.Column("domain_id", sa.String(36), nullable=False),
92 sa.Column("author_user_id", sa.String(36), nullable=True),
93 sa.Column("author_slug", sa.String(64), nullable=False),
94 sa.Column("slug", sa.String(64), nullable=False),
95 sa.Column("display_name", sa.String(255), nullable=False),
96 sa.Column("description", sa.Text(), nullable=False, server_default=""),
97 sa.Column("version", sa.String(32), nullable=False, server_default="1.0.0"),
98 sa.Column("manifest_hash", sa.String(64), nullable=False, server_default=""),
99 sa.Column("capabilities", sa.JSON(), nullable=False),
100 sa.Column("viewer_type", sa.String(64), nullable=False, server_default="generic"),
101 sa.Column("install_count", sa.Integer(), nullable=False, server_default="0"),
102 sa.Column("is_verified", sa.Boolean(), nullable=False, server_default="false"),
103 sa.Column("is_deprecated", sa.Boolean(), nullable=False, server_default="false"),
104 sa.Column("created_at", sa.DateTime(timezone=True), nullable=False,
105 server_default=sa.text("CURRENT_TIMESTAMP")),
106 sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False,
107 server_default=sa.text("CURRENT_TIMESTAMP")),
108 sa.PrimaryKeyConstraint("domain_id"),
109 sa.UniqueConstraint("author_slug", "slug", name="uq_musehub_domains_author_slug"),
110 )
111 op.create_index("ix_musehub_domains_author_slug", "musehub_domains", ["author_slug"])
112 op.create_index("ix_musehub_domains_slug", "musehub_domains", ["slug"])
113 op.create_index("ix_musehub_domains_author_user_id", "musehub_domains", ["author_user_id"])
114
115 # ── musehub_domain_installs ───────────────────────────────────────────────
116 op.create_table(
117 "musehub_domain_installs",
118 sa.Column("install_id", sa.String(36), nullable=False),
119 sa.Column("user_id", sa.String(36), nullable=False),
120 sa.Column("domain_id", sa.String(36), nullable=False),
121 sa.Column("created_at", sa.DateTime(timezone=True), nullable=False,
122 server_default=sa.text("CURRENT_TIMESTAMP")),
123 sa.PrimaryKeyConstraint("install_id"),
124 sa.UniqueConstraint("user_id", "domain_id", name="uq_musehub_domain_installs"),
125 )
126 op.create_index("ix_musehub_domain_installs_user_id", "musehub_domain_installs", ["user_id"])
127 op.create_index("ix_musehub_domain_installs_domain_id", "musehub_domain_installs", ["domain_id"])
128
129 # ── musehub_repos: add domain_id + domain_meta, drop key_signature + tempo_bpm ──
130 op.add_column("musehub_repos",
131 sa.Column("domain_id", sa.String(36), nullable=True))
132 op.add_column("musehub_repos",
133 sa.Column("domain_meta", sa.JSON(), nullable=False, server_default="{}"))
134 op.create_index("ix_musehub_repos_domain_id", "musehub_repos", ["domain_id"])
135 op.drop_column("musehub_repos", "key_signature")
136 op.drop_column("musehub_repos", "tempo_bpm")
137
138 # ── musehub_pr_comments: add dimension_ref, drop music-specific fields ───
139 op.add_column("musehub_pr_comments",
140 sa.Column("dimension_ref", sa.JSON(), nullable=False, server_default="{}"))
141 op.drop_column("musehub_pr_comments", "target_type")
142 op.drop_column("musehub_pr_comments", "target_track")
143 op.drop_column("musehub_pr_comments", "target_beat_start")
144 op.drop_column("musehub_pr_comments", "target_beat_end")
145 op.drop_column("musehub_pr_comments", "target_note_pitch")
146
147 # ── musehub_issue_comments: rename musical_refs → state_refs ─────────────
148 op.add_column("musehub_issue_comments",
149 sa.Column("state_refs", sa.JSON(), nullable=False, server_default="[]"))
150 # Copy existing data to new column
151 op.execute(
152 "UPDATE musehub_issue_comments SET state_refs = musical_refs"
153 )
154 op.drop_column("musehub_issue_comments", "musical_refs")
155
156 # ── musehub_render_jobs: rename domain-specific column names ──────────────
157 op.add_column("musehub_render_jobs",
158 sa.Column("artifact_count", sa.Integer(), nullable=False, server_default="0"))
159 op.add_column("musehub_render_jobs",
160 sa.Column("audio_object_ids", sa.JSON(), nullable=False, server_default="[]"))
161 op.add_column("musehub_render_jobs",
162 sa.Column("preview_object_ids", sa.JSON(), nullable=False, server_default="[]"))
163 # Copy existing data
164 op.execute("UPDATE musehub_render_jobs SET artifact_count = midi_count")
165 op.execute("UPDATE musehub_render_jobs SET audio_object_ids = mp3_object_ids")
166 op.execute("UPDATE musehub_render_jobs SET preview_object_ids = image_object_ids")
167 op.drop_column("musehub_render_jobs", "midi_count")
168 op.drop_column("musehub_render_jobs", "mp3_object_ids")
169 op.drop_column("musehub_render_jobs", "image_object_ids")
170
171 # ── Seed built-in .code domain ────────────────────────────────────────────
172 # Stable ID so tooling can reference it without querying the DB.
173 # Not a real UUID — valid VARCHAR(36) matching DOMAIN_CODE constant.
174 _DOMAIN_CODE_ID = "domain-code-gabriel-0001"
175
176 code_caps_json = json.dumps(_CODE_CAPABILITIES)
177
178 op.execute(
179 sa.text(
180 "INSERT INTO musehub_domains "
181 "(domain_id, author_user_id, author_slug, slug, display_name, description, "
182 "version, manifest_hash, capabilities, viewer_type, install_count, "
183 "is_verified, is_deprecated, created_at, updated_at) "
184 "VALUES (:did, NULL, 'gabriel', 'code', 'Code', "
185 "'Symbol-graph code state space — diff and merge at the level of named "
186 "functions, classes, and modules across Python, TypeScript, Go, Rust, "
187 "Java, C, C++, C#, Ruby, and Kotlin.', "
188 f"'1.0.0', :chash, CAST('{code_caps_json.replace(chr(39), chr(39)+chr(39))}' AS json), "
189 "'symbol_graph', 0, true, false, now(), now()) "
190 "ON CONFLICT (author_slug, slug) DO NOTHING"
191 ).bindparams(
192 did=_DOMAIN_CODE_ID,
193 chash=_manifest_hash(_CODE_CAPABILITIES),
194 )
195 )
196
197
198 def downgrade() -> None:
199 # Restore musehub_render_jobs original columns
200 op.add_column("musehub_render_jobs",
201 sa.Column("midi_count", sa.Integer(), nullable=False, server_default="0"))
202 op.add_column("musehub_render_jobs",
203 sa.Column("mp3_object_ids", sa.JSON(), nullable=False, server_default="[]"))
204 op.add_column("musehub_render_jobs",
205 sa.Column("image_object_ids", sa.JSON(), nullable=False, server_default="[]"))
206 op.execute("UPDATE musehub_render_jobs SET midi_count = artifact_count")
207 op.execute("UPDATE musehub_render_jobs SET mp3_object_ids = audio_object_ids")
208 op.execute("UPDATE musehub_render_jobs SET image_object_ids = preview_object_ids")
209 op.drop_column("musehub_render_jobs", "artifact_count")
210 op.drop_column("musehub_render_jobs", "audio_object_ids")
211 op.drop_column("musehub_render_jobs", "preview_object_ids")
212
213 # Restore musehub_issue_comments
214 op.add_column("musehub_issue_comments",
215 sa.Column("musical_refs", sa.JSON(), nullable=False, server_default="[]"))
216 op.execute("UPDATE musehub_issue_comments SET musical_refs = state_refs")
217 op.drop_column("musehub_issue_comments", "state_refs")
218
219 # Restore musehub_pr_comments
220 op.add_column("musehub_pr_comments",
221 sa.Column("target_type", sa.String(20), nullable=False, server_default="general"))
222 op.add_column("musehub_pr_comments",
223 sa.Column("target_track", sa.String(255), nullable=True))
224 op.add_column("musehub_pr_comments",
225 sa.Column("target_beat_start", sa.Float(), nullable=True))
226 op.add_column("musehub_pr_comments",
227 sa.Column("target_beat_end", sa.Float(), nullable=True))
228 op.add_column("musehub_pr_comments",
229 sa.Column("target_note_pitch", sa.Integer(), nullable=True))
230 op.drop_column("musehub_pr_comments", "dimension_ref")
231
232 # Restore musehub_repos
233 op.drop_index("ix_musehub_repos_domain_id", table_name="musehub_repos")
234 op.drop_column("musehub_repos", "domain_id")
235 op.drop_column("musehub_repos", "domain_meta")
236 op.add_column("musehub_repos",
237 sa.Column("key_signature", sa.String(50), nullable=True))
238 op.add_column("musehub_repos",
239 sa.Column("tempo_bpm", sa.Integer(), nullable=True))
240
241 # Drop new tables
242 op.drop_index("ix_musehub_domain_installs_domain_id", table_name="musehub_domain_installs")
243 op.drop_index("ix_musehub_domain_installs_user_id", table_name="musehub_domain_installs")
244 op.drop_table("musehub_domain_installs")
245
246 op.drop_index("ix_musehub_domains_author_user_id", table_name="musehub_domains")
247 op.drop_index("ix_musehub_domains_slug", table_name="musehub_domains")
248 op.drop_index("ix_musehub_domains_author_slug", table_name="musehub_domains")
249 op.drop_table("musehub_domains")
File History 1 commit
sha256:a10adeeb7a0169cb9900f9806ed7a973047258abb6283724fe55e8eb68ff3f0a init: musehub initial commit Human 171 days ago