fix: bound repository collection identity projection

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a06ed7-828d-7ca0-a8d4-0c3e5a0c4102
This commit is contained in:
tegwick 2026-09-05 02:03:52 +02:00
parent 4cef58cd98
commit 3578dc2f32
10 changed files with 3331 additions and 9 deletions

View file

@ -13,6 +13,7 @@ from dataclasses import dataclass
from fastapi import HTTPException
from sqlalchemy import func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import noload
from api.models.fabric_graph import FabricGraphEdge, FabricGraphImport, FabricGraphNode
from api.models.managed_repo import ManagedRepo
@ -95,6 +96,79 @@ async def resolve_repository_slug(
)
async def repository_resolutions_for_repositories(
session: AsyncSession,
repositories: list[ManagedRepo],
) -> dict[uuid.UUID, RepositorySlugResolution]:
"""Resolve a repository collection with one slug-registry query.
Collection callers already hold canonical ``ManagedRepo`` rows. Resolving
every row through :func:`resolve_repository_slug` would issue two identity
queries per repository. Load the complete alias sets once instead while
retaining the pre-registry compatibility behavior.
"""
if not repositories:
return {}
repository_ids = [repository.id for repository in repositories]
records = list(
(
await session.execute(
select(RepositorySlug)
.options(
noload(RepositorySlug.repo),
noload(RepositorySlug.source_operation),
)
.where(RepositorySlug.repo_id.in_(repository_ids))
.order_by(RepositorySlug.repo_id, RepositorySlug.kind.desc(), RepositorySlug.slug)
)
).scalars()
)
records_by_repository: dict[uuid.UUID, list[RepositorySlug]] = {
repository_id: [] for repository_id in repository_ids
}
for record in records:
records_by_repository[record.repo_id].append(record)
resolutions: dict[uuid.UUID, RepositorySlugResolution] = {}
for repository in repositories:
repository_records = records_by_repository[repository.id]
requested_record = next(
(record for record in repository_records if record.slug == repository.slug),
None,
)
if requested_record is None:
# Compatibility for databases upgraded before the identity backfill.
resolutions[repository.id] = RepositorySlugResolution(
repo=repository,
requested_slug=repository.slug,
canonical_slug=repository.slug,
slug_status="canonical",
aliases=(),
)
continue
canonicals = [record for record in repository_records if record.kind == "canonical"]
if len(canonicals) != 1 or canonicals[0].slug != repository.slug:
raise HTTPException(
status_code=409,
detail=f"Repository '{repository.id}' has inconsistent canonical slug state",
)
resolutions[repository.id] = RepositorySlugResolution(
repo=repository,
requested_slug=repository.slug,
canonical_slug=repository.slug,
slug_status=requested_record.kind,
aliases=tuple(
record.slug for record in repository_records if record.kind == "alias"
),
source_operation_id=requested_record.source_operation_id,
)
return resolutions
async def canonicalize_repository_slug(session: AsyncSession, value: str) -> str:
"""Canonicalize a value only when it is a registered repository identity."""