"""Artifact relationship APIs — explicit edges. All endpoints are tenant-scoped. Cross-tenant edges can never be created because both endpoint artifacts are validated against the tenant context. """ from __future__ import annotations from typing import Optional from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Query, status from asf_domain import ( ArtifactRelationship, ArtifactRelationshipCreate, RelationshipType, ) from ...repositories import NotFoundError, DuplicateError, TenantMismatchError from .deps import relationship_service from ...services import RelationshipService router = APIRouter() @router.post( "", response_model=ArtifactRelationship, status_code=status.HTTP_201_CREATED, summary="Create a directed edge: source target", ) async def create_relationship( body: ArtifactRelationshipCreate, svc: RelationshipService = Depends(relationship_service), ) -> ArtifactRelationship: try: return await svc.create(body) except DuplicateError as exc: raise HTTPException(status_code=409, detail=str(exc)) from exc except TenantMismatchError as exc: raise HTTPException(status_code=403, detail=str(exc)) from exc @router.delete( "/{relationship_id}", status_code=status.HTTP_204_NO_CONTENT, summary="Delete a relationship", ) async def delete_relationship( relationship_id: UUID, svc: RelationshipService = Depends(relationship_service), ) -> None: try: await svc.delete(relationship_id) except (NotFoundError, TenantMismatchError) as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc return None @router.get( "/from/{artifact_id}", response_model=list[ArtifactRelationship], summary="Outgoing edges: artifact → *", ) async def list_outgoing( artifact_id: UUID, relationship_type: Optional[RelationshipType] = Query(None), limit: int = Query(100, ge=1, le=500), svc: RelationshipService = Depends(relationship_service), ) -> list[ArtifactRelationship]: return await svc.outgoing(artifact_id, relationship_type=relationship_type, limit=limit) @router.get( "/to/{artifact_id}", response_model=list[ArtifactRelationship], summary="Incoming edges: * → artifact", ) async def list_incoming( artifact_id: UUID, relationship_type: Optional[RelationshipType] = Query(None), limit: int = Query(100, ge=1, le=500), svc: RelationshipService = Depends(relationship_service), ) -> list[ArtifactRelationship]: return await svc.incoming(artifact_id, relationship_type=relationship_type, limit=limit) @router.get( "/for-artifact/{artifact_id}", response_model=list[ArtifactRelationship], summary="All edges touching an artifact (in + out)", ) async def list_for_artifact( artifact_id: UUID, limit: int = Query(200, ge=1, le=1000), svc: RelationshipService = Depends(relationship_service), ) -> list[ArtifactRelationship]: return await svc.for_artifact(artifact_id, limit=limit)