Fix AP re-publish: use versioned object IDs after Delete
After Delete + re-Create, Mastodon tombstones the old object ID and ignores new Creates with the same ID. Now appends /v2, /v3 etc. so remote servers treat re-publishes as fresh posts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -288,6 +288,20 @@ class SqlFederationService:
|
||||
).scalars().first()
|
||||
return _activity_to_dto(a) if a else None
|
||||
|
||||
async def count_activities_for_source(
|
||||
self, session: AsyncSession, source_type: str, source_id: int,
|
||||
*, activity_type: str,
|
||||
) -> int:
|
||||
from sqlalchemy import func
|
||||
result = await session.execute(
|
||||
select(func.count()).select_from(APActivity).where(
|
||||
APActivity.source_type == source_type,
|
||||
APActivity.source_id == source_id,
|
||||
APActivity.activity_type == activity_type,
|
||||
)
|
||||
)
|
||||
return result.scalar_one()
|
||||
|
||||
# -- Followers ------------------------------------------------------------
|
||||
|
||||
async def get_followers(
|
||||
|
||||
@@ -56,13 +56,25 @@ async def try_publish(
|
||||
elif activity_type in ("Delete", "Update"):
|
||||
return # never published, nothing to delete/update
|
||||
|
||||
# Stable object ID: same source always gets the same object id so
|
||||
# Mastodon treats Create/Update/Delete as the same post.
|
||||
# Stable object ID within a publish cycle. After Delete + re-Create
|
||||
# we append a version suffix so remote servers (Mastodon) treat it as
|
||||
# a brand-new post rather than ignoring the tombstoned ID.
|
||||
domain = os.getenv("AP_DOMAIN", "rose-ash.com")
|
||||
object_data["id"] = (
|
||||
base_object_id = (
|
||||
f"https://{domain}/users/{actor.preferred_username}"
|
||||
f"/objects/{source_type.lower()}/{source_id}"
|
||||
)
|
||||
if activity_type == "Create" and existing and existing.activity_type == "Delete":
|
||||
# Count prior Creates to derive a version number
|
||||
create_count = await services.federation.count_activities_for_source(
|
||||
session, source_type, source_id, activity_type="Create",
|
||||
)
|
||||
object_data["id"] = f"{base_object_id}/v{create_count + 1}"
|
||||
elif activity_type in ("Update", "Delete") and existing and existing.object_data:
|
||||
# Use the same object ID as the most recent activity
|
||||
object_data["id"] = existing.object_data.get("id", base_object_id)
|
||||
else:
|
||||
object_data["id"] = base_object_id
|
||||
|
||||
try:
|
||||
await services.federation.publish_activity(
|
||||
|
||||
@@ -217,6 +217,9 @@ class StubFederationService:
|
||||
async def get_activity_for_source(self, session, source_type, source_id):
|
||||
return None
|
||||
|
||||
async def count_activities_for_source(self, session, source_type, source_id, *, activity_type):
|
||||
return 0
|
||||
|
||||
async def get_followers(self, session, username):
|
||||
return []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user