feat: make repository reads alias-aware
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a049a4-ee9f-78e1-9d66-2cb0f9bea3e3
This commit is contained in:
parent
639b9aed08
commit
2e2ae1e5d0
19 changed files with 982 additions and 94 deletions
|
|
@ -8,13 +8,17 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
from api.database import get_session
|
||||
from api.models.agent_message import AgentMessage
|
||||
from api.models.interface_change import InterfaceChange
|
||||
from api.models.managed_repo import ManagedRepo
|
||||
from api.models.progress_event import ProgressEvent
|
||||
from api.schemas.interface_change import (
|
||||
InterfaceChangeCreate,
|
||||
InterfaceChangePatch,
|
||||
InterfaceChangeRead,
|
||||
)
|
||||
from api.services.repository_aliases import (
|
||||
affected_slug_predicate,
|
||||
canonicalize_repository_slug,
|
||||
resolve_repository_slug,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/interface-changes", tags=["interface-changes"])
|
||||
|
||||
|
|
@ -32,7 +36,12 @@ async def create_interface_change(
|
|||
if body.change_type not in _VALID_CHANGE_TYPES:
|
||||
raise HTTPException(status_code=422, detail=f"change_type must be one of {sorted(_VALID_CHANGE_TYPES)}")
|
||||
|
||||
repo = await _repo_by_slug(body.repo_slug, session)
|
||||
resolution = await resolve_repository_slug(session, body.repo_slug)
|
||||
repo = resolution.repo
|
||||
affected_repo_slugs = [
|
||||
await canonicalize_repository_slug(session, slug)
|
||||
for slug in body.affected_repo_slugs
|
||||
]
|
||||
change = InterfaceChange(
|
||||
repo_id=repo.id,
|
||||
interface_type=body.interface_type,
|
||||
|
|
@ -40,7 +49,7 @@ async def create_interface_change(
|
|||
title=body.title,
|
||||
description=body.description,
|
||||
affected_paths=body.affected_paths,
|
||||
affected_repo_slugs=body.affected_repo_slugs,
|
||||
affected_repo_slugs=affected_repo_slugs,
|
||||
planned_for=body.planned_for,
|
||||
author=body.author,
|
||||
status="draft",
|
||||
|
|
@ -68,7 +77,11 @@ async def list_interface_changes(
|
|||
if change_type:
|
||||
q = q.where(InterfaceChange.change_type == change_type)
|
||||
if affected_repo:
|
||||
q = q.where(InterfaceChange.affected_repo_slugs.contains([affected_repo]))
|
||||
resolution = await resolve_repository_slug(
|
||||
session, affected_repo, required=False
|
||||
)
|
||||
values = resolution.slug_values if resolution else (affected_repo,)
|
||||
q = q.where(affected_slug_predicate(InterfaceChange.affected_repo_slugs, values))
|
||||
result = await session.execute(q)
|
||||
return [InterfaceChangeRead.from_orm_with_slug(c) for c in result.scalars().all()]
|
||||
|
||||
|
|
@ -94,7 +107,13 @@ async def patch_interface_change(
|
|||
status_code=409,
|
||||
detail=f"Cannot edit a change with status '{change.status}'. Only draft records are mutable.",
|
||||
)
|
||||
for field, value in body.model_dump(exclude_unset=True).items():
|
||||
payload = body.model_dump(exclude_unset=True)
|
||||
if payload.get("affected_repo_slugs") is not None:
|
||||
payload["affected_repo_slugs"] = [
|
||||
await canonicalize_repository_slug(session, slug)
|
||||
for slug in payload["affected_repo_slugs"]
|
||||
]
|
||||
for field, value in payload.items():
|
||||
setattr(change, field, value)
|
||||
await session.commit()
|
||||
await session.refresh(change)
|
||||
|
|
@ -119,12 +138,13 @@ async def publish_interface_change(
|
|||
# Send inbox notifications to agents of affected repos
|
||||
affected = change.affected_repo_slugs or []
|
||||
for slug in affected:
|
||||
target_slug = await canonicalize_repository_slug(session, slug)
|
||||
paths_summary = ", ".join(change.affected_paths[:5]) if change.affected_paths else "see description"
|
||||
if len(change.affected_paths) > 5:
|
||||
paths_summary += f" (+{len(change.affected_paths) - 5} more)"
|
||||
msg = AgentMessage(
|
||||
from_agent=change.repo.slug,
|
||||
to_agent=slug,
|
||||
to_agent=target_slug,
|
||||
subject=f"[{change.change_type.upper()}] {change.title}",
|
||||
body=(
|
||||
f"**Interface change published by `{change.repo.slug}`**\n\n"
|
||||
|
|
@ -174,12 +194,9 @@ async def resolve_interface_change(
|
|||
return InterfaceChangeRead.from_orm_with_slug(change)
|
||||
|
||||
|
||||
async def _repo_by_slug(slug: str, session: AsyncSession) -> ManagedRepo:
|
||||
result = await session.execute(select(ManagedRepo).where(ManagedRepo.slug == slug))
|
||||
repo = result.scalar_one_or_none()
|
||||
if repo is None:
|
||||
raise HTTPException(status_code=404, detail=f"Repo '{slug}' not found")
|
||||
return repo
|
||||
async def _repo_by_slug(slug: str, session: AsyncSession):
|
||||
resolution = await resolve_repository_slug(session, slug)
|
||||
return resolution.repo
|
||||
|
||||
|
||||
async def _get_or_404(change_id: uuid.UUID, session: AsyncSession) -> InterfaceChange:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue