gabriel / musehub public
feat patch social task/social-domain-phase02 #1 / 1
AI Agent gabriel · 142 days ago · May 2, 2026 · Diff

feat(social): Phase 02 — MuseHub Social API

Add three-layer Social API to musehub:

- GET /api/social/{handle}: cursor-paginated posts feed (reads social-domain repo snapshot manifest, loads post objects, sorts newest-first) - GET /api/social/{handle}/stream: SSE real-time subscription (delivers social_delta events when new social-domain pushes land for a handle) - musehub/services/musehub_social.py: in-process SSE subscriber registry (subscribe_handle / unsubscribe_handle / fan_out_to_subscribers) plus get_social_feed service that reads directly from the Muse object store

28/28 tests GREEN across TestSocialApiShape, TestSocialApiFeed, TestSocialFanOut, TestSocialFeedService, TestSocialApiDocstrings.

sha256:7473b37760ad25d37ddad07e9c1a6c671a33ee6ee9e3349056467cd1987493f6 sha
+109 symbols
sha256:c3dd819cae0e1b74a313c54853e3697ba00396775783b75cac1ecc9214563354 snapshot
+109
symbols added
0
dead code introduced
Semantic Changes 109 symbols
~ musehub/api/routes/musehub/social.py .py 27 symbols added
+ _SOCIAL_READ_LIMIT variable variable _SOCIAL_READ_LIMIT L40–40
+ APIRouter import import APIRouter L25–25
+ AsyncIterator import import AsyncIterator L23–23
+ AsyncSession import import AsyncSession L27–27
+ Depends import import Depends L25–25
+ HTTPException import import HTTPException L25–25
+ MIST_READ_LIMIT import import MIST_READ_LIMIT L32–32
+ Query import import Query L25–25
+ Request import import Request L25–25
+ SSE_CONTENT_TYPE import import SSE_CONTENT_TYPE L31–31
+ StreamingResponse import import StreamingResponse L26–26
+ TokenClaims import import TokenClaims L29–29
+ annotations import import annotations L18–18
+ asyncio import import asyncio L20–20
+ get_db import import get_db L30–30
+ get_msign_handle import import get_msign_handle L32–32
+ json import import json L21–21
+ limiter import import limiter L32–32
+ logging import import logging L22–22
+ musehub_social import import musehub_social L33–33
+ optional_token import import optional_token L29–29
+ sse_event import import sse_event L31–31
+ sse_heartbeat import import sse_heartbeat L31–31
+ logger variable variable logger L35–35
+ router variable variable router L37–37
+ social_feed function async_function social_feed L45–83
+ social_stream function async_function social_stream L87–131
~ musehub/services/musehub_social.py .py 19 symbols added
+ _subscribers variable variable _subscribers L41–41
+ _utc_now function function _utc_now L97–98
+ fan_out_to_subscribers function async_function fan_out_to_subscribers L76–90
+ get_social_feed function async_function get_social_feed L101–220
+ AsyncSession import import AsyncSession L28–28
+ JSONObject import import JSONObject L31–31
+ annotations import import annotations L18–18
+ asyncio import import asyncio L20–20
+ datetime import import datetime L24–24
+ db import import db L30–30
+ hashlib import import hashlib L21–21
+ json import import json L22–22
+ logging import import logging L23–23
+ msgpack import import msgpack L26–26
+ select import import select L27–27
+ timezone import import timezone L24–24
+ logger variable variable logger L33–33
+ subscribe_handle function function subscribe_handle L44–58
+ unsubscribe_handle function function unsubscribe_handle L61–73
~ tests/test_social_api.py .py 62 symbols added
+ TestSocialApiDocstrings class class TestSocialApiDocstrings L429–453
+ test_fan_out_has_docstring method method test_fan_out_has_docstring L451–453
+ test_feed_handler_has_docstring method method test_feed_handler_has_docstring L439–441
+ test_get_social_feed_service_has_docstring method method test_get_social_feed_service_has_docstring L447–449
+ test_route_module_has_docstring method method test_route_module_has_docstring L431–433
+ test_service_module_has_docstring method method test_service_module_has_docstring L435–437
+ test_stream_handler_has_docstring method method test_stream_handler_has_docstring L443–445
+ TestSocialApiFeed class class TestSocialApiFeed L208–293
+ test_feed_cursor_pagination method async_method test_feed_cursor_pagination L263–274
+ test_feed_empty_for_no_posts method async_method test_feed_empty_for_no_posts L286–293
+ test_feed_limit_parameter method async_method test_feed_limit_parameter L252–260
+ test_feed_no_cursor_on_last_page method async_method test_feed_no_cursor_on_last_page L277–283
+ test_feed_post_has_required_fields method async_method test_feed_post_has_required_fields L240–249
+ test_feed_posts_sorted_newest_first method async_method test_feed_posts_sorted_newest_first L226–237
+ test_feed_returns_all_posts method async_method test_feed_returns_all_posts L211–223
+ TestSocialApiShape class class TestSocialApiShape L153–201
+ test_feed_200_for_known_handle_no_posts method async_method test_feed_200_for_known_handle_no_posts L161–166
+ test_feed_404_for_unknown_handle method async_method test_feed_404_for_unknown_handle L156–158
+ test_feed_handle_matches_path method async_method test_feed_handle_matches_path L181–186
+ test_feed_response_has_expected_keys method async_method test_feed_response_has_expected_keys L169–178
+ test_stream_returns_streaming_response method method test_stream_returns_streaming_response L195–201
+ test_stream_route_registered method method test_stream_route_registered L188–193
+ TestSocialFanOut class class TestSocialFanOut L300–355
+ test_fan_out_delivers_event_to_subscriber method async_method test_fan_out_delivers_event_to_subscriber L303–317
+ test_fan_out_multiple_subscribers_all_receive method async_method test_fan_out_multiple_subscribers_all_receive L327–342
+ test_fan_out_no_subscribers_is_noop method async_method test_fan_out_no_subscribers_is_noop L320–324
+ test_unsubscribe_stops_delivery method async_method test_unsubscribe_stops_delivery L345–355
+ TestSocialFeedService class class TestSocialFeedService L362–422
+ test_get_social_feed_empty_when_no_social_repo method async_method test_get_social_feed_empty_when_no_social_repo L365–372
+ test_get_social_feed_limit method async_method test_get_social_feed_limit L403–412
+ test_get_social_feed_raises_404_not_found method async_method test_get_social_feed_raises_404_not_found L415–422
+ test_get_social_feed_returns_posts method async_method test_get_social_feed_returns_posts L375–386
+ test_get_social_feed_sorted_newest_first method async_method test_get_social_feed_sorted_newest_first L389–400
+ _OWNER variable variable _OWNER L44–44
+ _blob_id function function _blob_id L51–52
+ _post_bytes function function _post_bytes L59–60
+ _seed_social_repo function async_function _seed_social_repo L63–135
+ _utc_now function function _utc_now L55–56
+ client function async_function client L143–146
+ ASGITransport import import ASGITransport L27–27
+ AsyncClient import import AsyncClient L27–27
+ AsyncGenerator import import AsyncGenerator L20–20
+ AsyncSession import import AsyncSession L28–28
+ MusehubCommit import import MusehubCommit L31–31
+ MusehubObject import import MusehubObject L31–31
+ MusehubRepo import import MusehubRepo L31–31
+ MusehubSnapshot import import MusehubSnapshot L31–31
+ annotations import import annotations L14–14
+ anyio import import anyio L23–23
+ app import import app L37–37
+ asyncio import import asyncio L16–16
+ compute_identity_id import import compute_identity_id L30–30
+ compute_repo_id import import compute_repo_id L30–30
+ datetime import import datetime L21–21
+ hashlib import import hashlib L17–17
+ json import import json L18–18
+ msgpack import import msgpack L24–24
+ pytest import import pytest L25–25
+ pytest_asyncio import import pytest_asyncio L26–26
+ timedelta import import timedelta L21–21
+ timezone import import timezone L21–21
+ uuid import import uuid L19–19
~ musehub/main.py .py 1 symbol added
+ api_social_router import import api_social_router L65–65
← Older Oldest on task/social-domain-phase02
All commits
Newer → Latest on task/social-domain-phase02

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:7473b37760ad25d37ddad07e9c1a6c671a33ee6ee9e3349056467cd1987493f6 --body "your comment"