generated from coulomb/repo-seed
scope refactoring
This commit is contained in:
@@ -257,6 +257,11 @@ def _resolve_path(repo: dict) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def resolve_repo_path(repo: dict) -> str:
|
||||
"""Resolve the repo path using the same host-aware rules as DoI checks."""
|
||||
return _resolve_path(repo)
|
||||
|
||||
|
||||
def _get_sync(api_base: str, path: str, params: dict | None = None) -> object:
|
||||
url = f"{api_base}{path}"
|
||||
if params:
|
||||
|
||||
@@ -13,7 +13,12 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from api.config import settings
|
||||
from api.database import get_session
|
||||
from api.doi_engine import compute_fingerprint, evaluate as _doi_evaluate, evaluate_scope_health
|
||||
from api.doi_engine import (
|
||||
compute_fingerprint,
|
||||
evaluate as _doi_evaluate,
|
||||
evaluate_scope_health,
|
||||
resolve_repo_path,
|
||||
)
|
||||
from api.models.doi_cache import DOICache
|
||||
from api.models.domain import Domain
|
||||
from api.models.interface_change import InterfaceChange
|
||||
@@ -31,6 +36,7 @@ from api.schemas.managed_repo import (
|
||||
RepoDispatch,
|
||||
RepoPathRegister,
|
||||
RepoRead,
|
||||
RepoScopeHealth,
|
||||
RepoUpdate,
|
||||
ScopeIssueDetail,
|
||||
)
|
||||
@@ -346,6 +352,53 @@ async def get_repo_by_id(
|
||||
return repo
|
||||
|
||||
|
||||
@router.get("/scope-health", response_model=list[RepoScopeHealth])
|
||||
async def list_repo_scope_health(
|
||||
needs_review: bool | None = None,
|
||||
reachable_only: bool = False,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> list[RepoScopeHealth]:
|
||||
"""Return machine-readable SCOPE.md health for active repos.
|
||||
|
||||
Repo-scoping uses this to refresh only repos and SCOPE.md sections that
|
||||
need attention, without guessing from free-text DoI output.
|
||||
"""
|
||||
result = await session.execute(
|
||||
select(ManagedRepo, Domain.slug)
|
||||
.join(Domain, Domain.id == ManagedRepo.domain_id)
|
||||
.where(ManagedRepo.status == "active")
|
||||
.order_by(ManagedRepo.slug)
|
||||
)
|
||||
|
||||
entries: list[RepoScopeHealth] = []
|
||||
for repo, domain_slug in result.all():
|
||||
repo_dict = _repo_doi_dict(repo, domain_slug)
|
||||
resolved_path = resolve_repo_path(repo_dict)
|
||||
scope_issue_details = [
|
||||
ScopeIssueDetail(**issue)
|
||||
for issue in evaluate_scope_health(repo_dict)
|
||||
]
|
||||
scope_needs_review = any(
|
||||
issue.id in {"C5a", "C5b", "C5c"} and issue.status in {"fail", "warn"}
|
||||
for issue in scope_issue_details
|
||||
)
|
||||
entry = RepoScopeHealth(
|
||||
repo_slug=repo.slug,
|
||||
domain_slug=domain_slug,
|
||||
local_path=resolved_path or repo.local_path,
|
||||
path_available=bool(resolved_path),
|
||||
scope_needs_review=scope_needs_review,
|
||||
scope_issue_details=scope_issue_details,
|
||||
)
|
||||
if needs_review is not None and entry.scope_needs_review != needs_review:
|
||||
continue
|
||||
if reachable_only and not entry.path_available:
|
||||
continue
|
||||
entries.append(entry)
|
||||
|
||||
return entries
|
||||
|
||||
|
||||
@router.get("/{slug}", response_model=RepoRead)
|
||||
async def get_repo(
|
||||
slug: str,
|
||||
@@ -496,15 +549,7 @@ async def get_repo_dispatch(
|
||||
domain_obj = domain_result.scalar_one_or_none()
|
||||
scope_issue_details = [
|
||||
ScopeIssueDetail(**issue)
|
||||
for issue in evaluate_scope_health({
|
||||
"slug": repo.slug,
|
||||
"domain_slug": domain_obj.slug if domain_obj else None,
|
||||
"local_path": repo.local_path,
|
||||
"remote_url": repo.remote_url,
|
||||
"host_paths": repo.host_paths or {},
|
||||
"last_sbom_at": str(repo.last_sbom_at) if repo.last_sbom_at else None,
|
||||
"updated_at": str(repo.updated_at) if repo.updated_at else "",
|
||||
})
|
||||
for issue in evaluate_scope_health(_repo_doi_dict(repo, domain_obj.slug if domain_obj else None))
|
||||
]
|
||||
scope_needs_review = any(
|
||||
issue.id in {"C5a", "C5b", "C5c"} and issue.status in {"fail", "warn"}
|
||||
@@ -576,3 +621,15 @@ async def _get_repo_by_slug(slug: str, session: AsyncSession) -> ManagedRepo:
|
||||
if repo is None:
|
||||
raise HTTPException(status_code=404, detail=f"Repo '{slug}' not found")
|
||||
return repo
|
||||
|
||||
|
||||
def _repo_doi_dict(repo: ManagedRepo, domain_slug: str | None) -> dict:
|
||||
return {
|
||||
"slug": repo.slug,
|
||||
"domain_slug": domain_slug,
|
||||
"local_path": repo.local_path,
|
||||
"remote_url": repo.remote_url,
|
||||
"host_paths": repo.host_paths or {},
|
||||
"last_sbom_at": str(repo.last_sbom_at) if repo.last_sbom_at else None,
|
||||
"updated_at": str(repo.updated_at) if repo.updated_at else "",
|
||||
}
|
||||
|
||||
@@ -98,3 +98,12 @@ class RepoDispatch(BaseModel):
|
||||
scope_needs_review: bool
|
||||
scope_issue_details: list[ScopeIssueDetail]
|
||||
last_state_synced_at: datetime | None
|
||||
|
||||
|
||||
class RepoScopeHealth(BaseModel):
|
||||
repo_slug: str
|
||||
domain_slug: str | None = None
|
||||
local_path: str | None = None
|
||||
path_available: bool
|
||||
scope_needs_review: bool
|
||||
scope_issue_details: list[ScopeIssueDetail]
|
||||
|
||||
Reference in New Issue
Block a user