Unify domain_events + ap_activities into AP-shaped event bus
All cross-service events now flow through ap_activities with a unified EventProcessor. Internal events use visibility="internal"; federation activities use visibility="public" and get delivered by a wildcard handler. - Add processing columns to APActivity (process_state, actor_uri, etc.) - New emit_activity() / register_activity_handler() API - EventProcessor polls ap_activities instead of domain_events - Rewrite all handlers to accept APActivity - Migrate all 7 emit_event call sites to emit_activity - publish_activity() sets process_state=pending directly (no emit_event bridge) - Migration to drop domain_events table Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -183,16 +183,21 @@ class SqlFederationService:
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
actor_url = f"https://{domain}/users/{username}"
|
||||
|
||||
activity = APActivity(
|
||||
activity_id=activity_uri,
|
||||
activity_type=activity_type,
|
||||
actor_profile_id=actor.id,
|
||||
actor_uri=actor_url,
|
||||
object_type=object_type,
|
||||
object_data=object_data,
|
||||
published=now,
|
||||
is_local=True,
|
||||
source_type=source_type,
|
||||
source_id=source_id,
|
||||
visibility="public",
|
||||
process_state="pending",
|
||||
)
|
||||
session.add(activity)
|
||||
await session.flush()
|
||||
@@ -208,7 +213,7 @@ class SqlFederationService:
|
||||
],
|
||||
"id": activity_uri,
|
||||
"type": activity_type,
|
||||
"actor": f"https://{domain}/users/{username}",
|
||||
"actor": actor_url,
|
||||
"published": now.isoformat(),
|
||||
"object": {
|
||||
"type": object_type,
|
||||
@@ -221,21 +226,6 @@ class SqlFederationService:
|
||||
except Exception:
|
||||
pass # IPFS failure is non-fatal
|
||||
|
||||
# Emit domain event for downstream processing (delivery)
|
||||
from shared.events import emit_event
|
||||
await emit_event(
|
||||
session,
|
||||
"federation.activity_created",
|
||||
"APActivity",
|
||||
activity.id,
|
||||
{
|
||||
"activity_id": activity.activity_id,
|
||||
"activity_type": activity_type,
|
||||
"actor_username": username,
|
||||
"object_type": object_type,
|
||||
},
|
||||
)
|
||||
|
||||
return _activity_to_dto(activity)
|
||||
|
||||
# -- Queries --------------------------------------------------------------
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"""Inline federation publication — called at write time, not via async handler.
|
||||
|
||||
Replaces the old pattern where emit_event("post.published") → async handler →
|
||||
publish_activity(). Now the originating service calls try_publish() directly,
|
||||
which creates the APActivity in the same DB transaction. AP delivery
|
||||
(federation.activity_created → inbox POST) stays async.
|
||||
The originating service calls try_publish() directly, which creates the
|
||||
APActivity (with process_state='pending') in the same DB transaction.
|
||||
The EventProcessor picks it up and the delivery wildcard handler POSTs
|
||||
to follower inboxes.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from shared.events import emit_event
|
||||
from shared.events import emit_activity
|
||||
from shared.models.container_relation import ContainerRelation
|
||||
|
||||
|
||||
@@ -40,17 +40,19 @@ async def attach_child(
|
||||
if label is not None:
|
||||
existing.label = label
|
||||
await session.flush()
|
||||
await emit_event(
|
||||
await emit_activity(
|
||||
session,
|
||||
event_type="container.child_attached",
|
||||
aggregate_type="container_relation",
|
||||
aggregate_id=existing.id,
|
||||
payload={
|
||||
activity_type="Add",
|
||||
actor_uri="internal:system",
|
||||
object_type="rose:ContainerRelation",
|
||||
object_data={
|
||||
"parent_type": parent_type,
|
||||
"parent_id": parent_id,
|
||||
"child_type": child_type,
|
||||
"child_id": child_id,
|
||||
},
|
||||
source_type="container_relation",
|
||||
source_id=existing.id,
|
||||
)
|
||||
return existing
|
||||
# Already attached and active — no-op
|
||||
@@ -77,17 +79,19 @@ async def attach_child(
|
||||
session.add(rel)
|
||||
await session.flush()
|
||||
|
||||
await emit_event(
|
||||
await emit_activity(
|
||||
session,
|
||||
event_type="container.child_attached",
|
||||
aggregate_type="container_relation",
|
||||
aggregate_id=rel.id,
|
||||
payload={
|
||||
activity_type="Add",
|
||||
actor_uri="internal:system",
|
||||
object_type="rose:ContainerRelation",
|
||||
object_data={
|
||||
"parent_type": parent_type,
|
||||
"parent_id": parent_id,
|
||||
"child_type": child_type,
|
||||
"child_id": child_id,
|
||||
},
|
||||
source_type="container_relation",
|
||||
source_id=rel.id,
|
||||
)
|
||||
|
||||
return rel
|
||||
@@ -139,17 +143,19 @@ async def detach_child(
|
||||
rel.deleted_at = func.now()
|
||||
await session.flush()
|
||||
|
||||
await emit_event(
|
||||
await emit_activity(
|
||||
session,
|
||||
event_type="container.child_detached",
|
||||
aggregate_type="container_relation",
|
||||
aggregate_id=rel.id,
|
||||
payload={
|
||||
activity_type="Remove",
|
||||
actor_uri="internal:system",
|
||||
object_type="rose:ContainerRelation",
|
||||
object_data={
|
||||
"parent_type": parent_type,
|
||||
"parent_id": parent_id,
|
||||
"child_type": child_type,
|
||||
"child_id": child_id,
|
||||
},
|
||||
source_type="container_relation",
|
||||
source_id=rel.id,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user