gabriel / musehub public
releases.py python
239 lines 9.1 KB
Raw
sha256:34035d72cef530c1ab9d6a6f53be18d803dde705fc3157617d70352a96d0747b Merge branch 'fix/wire-push-external-parent-manifest' into dev Human 8 days ago
1 """Write executors for release operations: create_release, attach_release_asset, delete_release_asset."""
2
3 import logging
4
5 from musehub.types.json_types import JSONObject
6 from musehub.db.database import AsyncSessionLocal
7 from musehub.services import musehub_releases, musehub_repository
8 from musehub.services.musehub_mcp_executor import MusehubToolResult, _check_db_available
9 from musehub.mcp.write_tools.issues import _require_write_access
10
11 logger = logging.getLogger(__name__)
12
13
14 async def execute_create_release(
15 *,
16 repo_id: str,
17 tag: str,
18 title: str = "",
19 body: str = "",
20 commit_id: str | None = None,
21 channel: str = "stable",
22 actor: str = "",
23 ) -> MusehubToolResult:
24 """Publish a new release for a MuseHub repository.
25
26 A release pins a semver tag to a specific commit. The ``channel`` field
27 replaces the old ``is_prerelease`` boolean with a named distribution tier
28 (stable | beta | alpha | nightly). Tags must be unique per repo.
29
30 Args:
31 repo_id: sha256 genesis ID of the repository.
32 tag: Semver tag string (e.g. ``"v1.2.3"``). Must be unique per repo.
33 title: Human-readable release title.
34 body: Markdown release notes.
35 commit_id: Optional commit sha256 genesis ID to pin this release to.
36 channel: Distribution channel: stable | beta | alpha | nightly.
37 actor: Authenticated user ID (MSign handle).
38
39 Returns:
40 ``MusehubToolResult`` with ``data.release_id`` and ``data.tag`` on success.
41 """
42 if (err := _check_db_available()) is not None:
43 return err
44
45 try:
46 async with AsyncSessionLocal() as session:
47 repo = await musehub_repository.get_repo(session, repo_id)
48 if repo is None:
49 return MusehubToolResult(
50 ok=False,
51 error_code="repo_not_found",
52 error_message=f"Repository '{repo_id}' not found.",
53 hint="Call musehub_search_repos() to find available repositories, or musehub_set_context(owner, slug) to focus on a repo.",
54 )
55 if (err := await _require_write_access(session, repo_id, actor, repo.owner)) is not None:
56 return err
57
58 release = await musehub_releases.create_release(
59 session,
60 repo_id=repo_id,
61 tag=tag,
62 title=title,
63 body=body,
64 commit_id=commit_id,
65 channel=channel,
66 author=actor,
67 )
68 await session.commit()
69 data: JSONObject = {
70 "release_id": release.release_id,
71 "repo_id": repo_id,
72 "tag": release.tag,
73 "title": release.title,
74 "body": release.body,
75 "channel": release.channel,
76 "commit_id": release.commit_id,
77 "author": release.author,
78 "created_at": release.created_at.isoformat() if release.created_at else None,
79 }
80 logger.info("MCP create_release %s for repo %s: %s", tag, repo_id, title)
81 return MusehubToolResult(ok=True, data=data)
82 except ValueError as exc:
83 return MusehubToolResult(
84 ok=False,
85 error_code="invalid_args",
86 error_message=str(exc),
87 )
88 except Exception as exc:
89 logger.exception("MCP create_release failed: %s", exc)
90 return MusehubToolResult(
91 ok=False,
92 error_code="invalid_args",
93 error_message=str(exc),
94 )
95
96
97 async def execute_attach_release_asset(
98 *,
99 repo_id: str,
100 tag: str,
101 name: str,
102 download_url: str,
103 label: str = "",
104 content_type: str = "",
105 size: int = 0,
106 actor: str = "",
107 ) -> MusehubToolResult:
108 """Attach a downloadable asset file to an existing release.
109
110 Assets represent build artifacts, MIDI bundles, model checkpoints, or any
111 other file associated with a release. The caller must be the repo owner or
112 a write/admin collaborator.
113
114 Args:
115 repo_id: sha256 genesis ID of the repository.
116 tag: Release tag the asset belongs to (e.g. ``"v1.2.3"``).
117 name: Filename shown in the UI (e.g. ``"myapp-v1.2.3-linux.tar.gz"``).
118 download_url: Direct download URL for the artifact.
119 label: Optional human-readable label (e.g. ``"Linux bundle"``).
120 content_type: MIME type of the artifact.
121 size: File size in bytes (0 if unknown).
122 actor: Authenticated user ID (MSign handle).
123
124 Returns:
125 ``MusehubToolResult`` with ``data.asset_id`` on success.
126 """
127 if (err := _check_db_available()) is not None:
128 return err
129
130 try:
131 async with AsyncSessionLocal() as session:
132 repo = await musehub_repository.get_repo(session, repo_id)
133 if repo is None:
134 return MusehubToolResult(
135 ok=False,
136 error_code="repo_not_found",
137 error_message=f"Repository '{repo_id}' not found.",
138 hint="Call musehub_search_repos() to find available repositories.",
139 )
140 if (err := await _require_write_access(session, repo_id, actor, repo.owner)) is not None:
141 return err
142
143 release = await musehub_releases.get_release_by_tag(session, repo_id, tag)
144 if release is None:
145 return MusehubToolResult(
146 ok=False,
147 error_code="release_not_found",
148 error_message=f"Release '{tag}' not found in repo '{repo_id}'.",
149 hint="Call musehub_list_releases() to see available releases.",
150 )
151
152 asset = await musehub_releases.attach_asset(
153 session,
154 release_id=release.release_id,
155 repo_id=repo_id,
156 name=name,
157 label=label,
158 content_type=content_type,
159 size=size,
160 download_url=download_url,
161 )
162 await session.commit()
163 data: JSONObject = {
164 "asset_id": asset.asset_id,
165 "release_id": asset.release_id,
166 "repo_id": repo_id,
167 "name": asset.name,
168 "label": asset.label,
169 "content_type": asset.content_type,
170 "size": asset.size,
171 "download_url": asset.download_url,
172 "download_count": asset.download_count,
173 }
174 logger.info("MCP attach_release_asset %s to %s/%s by %s", name, repo_id, tag, actor)
175 return MusehubToolResult(ok=True, data=data)
176 except Exception as exc:
177 logger.exception("MCP attach_release_asset failed: %s", exc)
178 return MusehubToolResult(ok=False, error_code="invalid_args", error_message=str(exc))
179
180
181 async def execute_delete_release_asset(
182 *,
183 repo_id: str,
184 tag: str,
185 asset_id: str,
186 actor: str = "",
187 ) -> MusehubToolResult:
188 """Remove an asset from a release.
189
190 Permanently deletes the asset record. The caller must be the repo owner or
191 a write/admin collaborator. The asset must belong to the specified release.
192
193 Args:
194 repo_id: sha256 genesis ID of the repository.
195 tag: Release tag the asset belongs to.
196 asset_id: ID of the asset to remove.
197 actor: Authenticated user ID (MSign handle).
198
199 Returns:
200 ``MusehubToolResult`` with ``data.deleted=true`` on success.
201 """
202 if (err := _check_db_available()) is not None:
203 return err
204
205 try:
206 async with AsyncSessionLocal() as session:
207 repo = await musehub_repository.get_repo(session, repo_id)
208 if repo is None:
209 return MusehubToolResult(
210 ok=False,
211 error_code="repo_not_found",
212 error_message=f"Repository '{repo_id}' not found.",
213 )
214 if (err := await _require_write_access(session, repo_id, actor, repo.owner)) is not None:
215 return err
216
217 release = await musehub_releases.get_release_by_tag(session, repo_id, tag)
218 if release is None:
219 return MusehubToolResult(
220 ok=False,
221 error_code="release_not_found",
222 error_message=f"Release '{tag}' not found in repo '{repo_id}'.",
223 )
224
225 asset = await musehub_releases.get_asset(session, asset_id)
226 if asset is None or asset.release_id != release.release_id:
227 return MusehubToolResult(
228 ok=False,
229 error_code="asset_not_found",
230 error_message=f"Asset '{asset_id}' not found on release '{tag}'.",
231 )
232
233 await musehub_releases.remove_asset(session, asset_id)
234 await session.commit()
235 logger.info("MCP delete_release_asset %s from %s/%s by %s", asset_id, repo_id, tag, actor)
236 return MusehubToolResult(ok=True, data={"deleted": True, "asset_id": asset_id})
237 except Exception as exc:
238 logger.exception("MCP delete_release_asset failed: %s", exc)
239 return MusehubToolResult(ok=False, error_code="invalid_args", error_message=str(exc))
File History 14 commits
sha256:34035d72cef530c1ab9d6a6f53be18d803dde705fc3157617d70352a96d0747b Merge branch 'fix/wire-push-external-parent-manifest' into dev Human 8 days ago
sha256:fc04e4cae9e1774d6a21b65c45daeed0e6787eb581d13aa1b03bfe9384a34226 Merge branch 'fix/two-column-scroll-layout' into dev Human 8 days ago
sha256:408916fc5973ba59c6e4eebaa80ebdcc801c0a63205651e25009d11548f79454 chore: bump version to 0.2.0.dev2 — nightly.2, matching muse Sonnet 4.6 patch 11 days ago
sha256:d035733f21ccff27735fddebfbbe0ed24565a32a22db8de5885402262671ecd2 chore: bump version to 0.2.0rc15 for musehub#113 fix release Sonnet 4.6 patch 14 days ago
sha256:0032d6cfa33bc3c8367436ad768e7dd0e339b4332153160247da8266cb5fa352 Merge branch 'task/version-tags-phase3-server' into dev Human 17 days ago
sha256:4669620efda9ff41c55bdefd1f7bfe1c239d468428744c84ead9957e5a003a53 merge: rescue snapshot-recovery hardening (c00aa21d) into d… Opus 4.8 minor 29 days ago
sha256:a59da49c4611b970fc4b6ae48678ce4943261c213a07ddbd73ce9201df869b4a fix: remove false-positive proposal_comments index drop fro… Sonnet 4.6 patch 33 days ago
sha256:0a240d6dbff234f07d98a28a4a9a68db702f3f9ff9260196f24219bdb1c0b6f3 feat: render markdown mists as HTML with heading anchor links Sonnet 4.6 patch 34 days ago
sha256:24a7d47486ebc4ebd1832830580e177ec6f877b48dced8c000e198cdec4ce9d6 Merge 'task/bump-version-rc12' into 'dev' — proposal: Bump … Human 35 days ago
sha256:b9ff931d147e0114a1f17060f415b89ed551c170a91ff226c70437aa5c85f9ee Merge 'task/bump-version-rc12' into 'dev' — proposal: Bump … Human 35 days ago
sha256:d1122d21e73471879b460037b22c0b50fded7c423444a176f248428f75dac39c Merge 'task/fix-issue-pagination-cursor' into 'dev' — propo… Human 35 days ago
sha256:01e18975e73d2b3cd5b6db7929c895bef9aa6e0d4391dc5b2adfc548b41318dd Merge 'feat/adding-debug-logs-to-staging' into 'dev' — prop… Human 35 days ago
sha256:6b1949fc2797ca4c1936a637a4cbfec828ef56cf52398a2e74ca3c4f494e728f fix: use wire_bytes not mpack_bytes_raw in compute_object_b… Sonnet 4.6 patch 48 days ago
sha256:b99f2455dc346966d040133f5203297e6e3ef5803a93728a2c30568d0a0f7583 rename: delta_add → delta_upsert across wire format, models… Sonnet 4.6 patch 50 days ago