diff --git a/services/relationships.py b/services/relationships.py index b0aa5f4..e1f4dac 100644 --- a/services/relationships.py +++ b/services/relationships.py @@ -18,7 +18,44 @@ async def attach_child( ) -> ContainerRelation: """ Create a ContainerRelation and emit container.child_attached event. + + Upsert behaviour: if a relation already exists (including soft-deleted), + revive it instead of inserting a duplicate. """ + # Check for existing (including soft-deleted) + existing = await session.scalar( + select(ContainerRelation).where( + ContainerRelation.parent_type == parent_type, + ContainerRelation.parent_id == parent_id, + ContainerRelation.child_type == child_type, + ContainerRelation.child_id == child_id, + ) + ) + if existing: + if existing.deleted_at is not None: + # Revive soft-deleted relation + existing.deleted_at = None + if sort_order is not None: + existing.sort_order = sort_order + if label is not None: + existing.label = label + await session.flush() + await emit_event( + session, + event_type="container.child_attached", + aggregate_type="container_relation", + aggregate_id=existing.id, + payload={ + "parent_type": parent_type, + "parent_id": parent_id, + "child_type": child_type, + "child_id": child_id, + }, + ) + return existing + # Already attached and active — no-op + return existing + if sort_order is None: max_order = await session.scalar( select(func.max(ContainerRelation.sort_order)).where(