API-completeness polish

This commit is contained in:
2026-04-26 09:12:27 +02:00
parent 70feabe965
commit d786589dd1
2 changed files with 364 additions and 26 deletions

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
from dataclasses import asdict from dataclasses import asdict
from pathlib import Path from pathlib import Path
from typing import Any
from fastapi import Depends, FastAPI, HTTPException, Query from fastapi import Depends, FastAPI, HTTPException, Query
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
@@ -324,6 +325,220 @@ class CandidateEvidenceMerge(BaseModel):
} }
class RepositoryResponse(BaseModel):
id: int
name: str
url: str
description: str | None
branch: str
status: str
class RepositorySnapshotResponse(BaseModel):
id: int
repository_id: int
commit_hash: str
branch: str
source_path: str
file_count: int
class AnalysisRunResponse(BaseModel):
id: int
repository_id: int
snapshot_id: int | None
status: str
started_at: str
completed_at: str | None
error_message: str | None
scanner_version: str
class ReviewDecisionResponse(BaseModel):
id: int
repository_id: int
analysis_run_id: int | None
action: str
notes: str
created_at: str
class ObservedFactResponse(BaseModel):
id: int
repository_id: int
analysis_run_id: int
snapshot_id: int | None
kind: str
path: str
name: str
value: str
metadata: dict[str, Any]
class ContentChunkResponse(BaseModel):
id: int
repository_id: int
analysis_run_id: int
snapshot_id: int | None
path: str
kind: str
start_line: int
end_line: int
text: str
class ScanSummaryResponse(BaseModel):
analysis_run: AnalysisRunResponse
snapshot: RepositorySnapshotResponse | None
facts: list[ObservedFactResponse]
class SourceReferenceResponse(BaseModel):
fact_id: int | None
path: str
kind: str
name: str
line: int | None = None
class CandidateEvidenceResponse(BaseModel):
id: int
type: str
reference: str
strength: str
status: str
source_refs: list[SourceReferenceResponse]
class CandidateFeatureResponse(BaseModel):
id: int
name: str
type: str
location: str
confidence: float
status: str
source_refs: list[SourceReferenceResponse]
confidence_label: str
class CandidateCapabilityResponse(BaseModel):
id: int
name: str
description: str
inputs: list[str]
outputs: list[str]
confidence: float
status: str
source_refs: list[SourceReferenceResponse]
confidence_label: str
features: list[CandidateFeatureResponse]
evidence: list[CandidateEvidenceResponse]
class CandidateAbilityResponse(BaseModel):
id: int
name: str
description: str
confidence: float
status: str
source_refs: list[SourceReferenceResponse]
confidence_label: str
capabilities: list[CandidateCapabilityResponse]
class CandidateGraphResponse(BaseModel):
repository: RepositoryResponse
analysis_run: AnalysisRunResponse
abilities: list[CandidateAbilityResponse]
class EvidenceResponse(BaseModel):
id: int
type: str
reference: str
strength: str
source_refs: list[SourceReferenceResponse]
class FeatureResponse(BaseModel):
id: int
name: str
type: str
location: str
confidence: float
confidence_label: str
source_refs: list[SourceReferenceResponse]
class CapabilityResponse(BaseModel):
id: int
name: str
description: str
inputs: list[str]
outputs: list[str]
confidence: float
confidence_label: str
features: list[FeatureResponse]
evidence: list[EvidenceResponse]
class AbilityResponse(BaseModel):
id: int
name: str
description: str
confidence: float
confidence_label: str
capabilities: list[CapabilityResponse]
class RepositoryAbilityMapResponse(BaseModel):
repository: RepositoryResponse
abilities: list[AbilityResponse]
class IdResponse(BaseModel):
id: int
class SearchResultResponse(BaseModel):
repository_id: int
repository_name: str
match_type: str
match_name: str
confidence: float
confidence_label: str
match_description: str
matched_field: str
ability_id: int | None = None
ability_name: str | None = None
capability_id: int | None = None
capability_name: str | None = None
evidence_level: str | None = None
source_reference: str | None = None
class AbilitySummaryResponse(BaseModel):
id: int
repository_id: int
repository_name: str
name: str
description: str
confidence: float
confidence_label: str
class CapabilitySummaryResponse(BaseModel):
id: int
repository_id: int
repository_name: str
ability_id: int
ability_name: str
name: str
description: str
confidence: float
confidence_label: str
API_DESCRIPTION = ( API_DESCRIPTION = (
"Register repositories, analyze their observable implementation facts, " "Register repositories, analyze their observable implementation facts, "
"curate reviewable ability graphs, and search approved repository abilities." "curate reviewable ability graphs, and search approved repository abilities."
@@ -356,7 +571,12 @@ def health() -> dict[str, str]:
return {"status": "ok"} return {"status": "ok"}
@app.post("/repos", status_code=201, tags=["repositories"]) @app.post(
"/repos",
status_code=201,
tags=["repositories"],
response_model=RepositoryResponse,
)
def create_repository( def create_repository(
payload: RepositoryCreate, payload: RepositoryCreate,
service: RegistryService = Depends(get_service), service: RegistryService = Depends(get_service),
@@ -368,14 +588,18 @@ def create_repository(
return asdict(repository) return asdict(repository)
@app.get("/repos", tags=["repositories"]) @app.get("/repos", tags=["repositories"], response_model=list[RepositoryResponse])
def list_repositories( def list_repositories(
service: RegistryService = Depends(get_service), service: RegistryService = Depends(get_service),
) -> list[dict[str, object]]: ) -> list[dict[str, object]]:
return [asdict(repository) for repository in service.list_repositories()] return [asdict(repository) for repository in service.list_repositories()]
@app.get("/repos/{repository_id}", tags=["repositories"]) @app.get(
"/repos/{repository_id}",
tags=["repositories"],
response_model=RepositoryResponse,
)
def get_repository( def get_repository(
repository_id: int, repository_id: int,
service: RegistryService = Depends(get_service), service: RegistryService = Depends(get_service),
@@ -386,7 +610,11 @@ def get_repository(
raise HTTPException(status_code=404, detail=str(exc)) from exc raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.patch("/repos/{repository_id}", tags=["repositories"]) @app.patch(
"/repos/{repository_id}",
tags=["repositories"],
response_model=RepositoryResponse,
)
def update_repository( def update_repository(
repository_id: int, repository_id: int,
payload: RepositoryUpdate, payload: RepositoryUpdate,
@@ -414,7 +642,12 @@ def delete_repository(
raise HTTPException(status_code=404, detail=str(exc)) from exc raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.post("/repos/{repository_id}/analysis-runs", status_code=201, tags=["analysis"]) @app.post(
"/repos/{repository_id}/analysis-runs",
status_code=201,
tags=["analysis"],
response_model=ScanSummaryResponse,
)
def create_analysis_run( def create_analysis_run(
repository_id: int, repository_id: int,
payload: AnalysisRunCreate, payload: AnalysisRunCreate,
@@ -430,7 +663,11 @@ def create_analysis_run(
return asdict(summary) return asdict(summary)
@app.get("/repos/{repository_id}/analysis-runs", tags=["analysis"]) @app.get(
"/repos/{repository_id}/analysis-runs",
tags=["analysis"],
response_model=list[AnalysisRunResponse],
)
def list_analysis_runs( def list_analysis_runs(
repository_id: int, repository_id: int,
service: RegistryService = Depends(get_service), service: RegistryService = Depends(get_service),
@@ -441,7 +678,11 @@ def list_analysis_runs(
raise HTTPException(status_code=404, detail=str(exc)) from exc raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/repos/{repository_id}/analysis-runs/{analysis_run_id}", tags=["analysis"]) @app.get(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}",
tags=["analysis"],
response_model=AnalysisRunResponse,
)
def get_analysis_run( def get_analysis_run(
repository_id: int, repository_id: int,
analysis_run_id: int, analysis_run_id: int,
@@ -453,7 +694,11 @@ def get_analysis_run(
raise HTTPException(status_code=404, detail=str(exc)) from exc raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/repos/{repository_id}/review-decisions", tags=["review"]) @app.get(
"/repos/{repository_id}/review-decisions",
tags=["review"],
response_model=list[ReviewDecisionResponse],
)
def list_repository_review_decisions( def list_repository_review_decisions(
repository_id: int, repository_id: int,
service: RegistryService = Depends(get_service), service: RegistryService = Depends(get_service),
@@ -470,6 +715,7 @@ def list_repository_review_decisions(
@app.get( @app.get(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}/review-decisions", "/repos/{repository_id}/analysis-runs/{analysis_run_id}/review-decisions",
tags=["review"], tags=["review"],
response_model=list[ReviewDecisionResponse],
) )
def list_analysis_run_review_decisions( def list_analysis_run_review_decisions(
repository_id: int, repository_id: int,
@@ -488,7 +734,11 @@ def list_analysis_run_review_decisions(
raise HTTPException(status_code=404, detail=str(exc)) from exc raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/repos/{repository_id}/observed-facts", tags=["analysis"]) @app.get(
"/repos/{repository_id}/observed-facts",
tags=["analysis"],
response_model=list[ObservedFactResponse],
)
def list_observed_facts( def list_observed_facts(
repository_id: int, repository_id: int,
analysis_run_id: int | None = None, analysis_run_id: int | None = None,
@@ -503,7 +753,11 @@ def list_observed_facts(
raise HTTPException(status_code=404, detail=str(exc)) from exc raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/repos/{repository_id}/content-chunks", tags=["analysis"]) @app.get(
"/repos/{repository_id}/content-chunks",
tags=["analysis"],
response_model=list[ContentChunkResponse],
)
def list_content_chunks( def list_content_chunks(
repository_id: int, repository_id: int,
analysis_run_id: int | None = None, analysis_run_id: int | None = None,
@@ -521,6 +775,7 @@ def list_content_chunks(
@app.get( @app.get(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}/content-chunks", "/repos/{repository_id}/analysis-runs/{analysis_run_id}/content-chunks",
tags=["analysis"], tags=["analysis"],
response_model=list[ContentChunkResponse],
) )
def list_analysis_run_content_chunks( def list_analysis_run_content_chunks(
repository_id: int, repository_id: int,
@@ -539,6 +794,7 @@ def list_analysis_run_content_chunks(
@app.get( @app.get(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}/candidate-graph", "/repos/{repository_id}/analysis-runs/{analysis_run_id}/candidate-graph",
tags=["review"], tags=["review"],
response_model=CandidateGraphResponse,
) )
def get_candidate_graph( def get_candidate_graph(
repository_id: int, repository_id: int,
@@ -554,6 +810,7 @@ def get_candidate_graph(
@app.post( @app.post(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}/candidate-graph/approve", "/repos/{repository_id}/analysis-runs/{analysis_run_id}/candidate-graph/approve",
tags=["review"], tags=["review"],
response_model=RepositoryAbilityMapResponse,
) )
def approve_candidate_graph( def approve_candidate_graph(
repository_id: int, repository_id: int,
@@ -577,6 +834,7 @@ def approve_candidate_graph(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}" "/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-abilities/{candidate_ability_id}/reject", "/candidate-abilities/{candidate_ability_id}/reject",
tags=["review"], tags=["review"],
response_model=CandidateGraphResponse,
) )
def reject_candidate_ability( def reject_candidate_ability(
repository_id: int, repository_id: int,
@@ -602,6 +860,7 @@ def reject_candidate_ability(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}" "/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-capabilities/{candidate_capability_id}/reject", "/candidate-capabilities/{candidate_capability_id}/reject",
tags=["review"], tags=["review"],
response_model=CandidateGraphResponse,
) )
def reject_candidate_capability( def reject_candidate_capability(
repository_id: int, repository_id: int,
@@ -627,6 +886,7 @@ def reject_candidate_capability(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}" "/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-features/{candidate_feature_id}/reject", "/candidate-features/{candidate_feature_id}/reject",
tags=["review"], tags=["review"],
response_model=CandidateGraphResponse,
) )
def reject_candidate_feature( def reject_candidate_feature(
repository_id: int, repository_id: int,
@@ -652,6 +912,7 @@ def reject_candidate_feature(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}" "/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-evidence/{candidate_evidence_id}/reject", "/candidate-evidence/{candidate_evidence_id}/reject",
tags=["review"], tags=["review"],
response_model=CandidateGraphResponse,
) )
def reject_candidate_evidence( def reject_candidate_evidence(
repository_id: int, repository_id: int,
@@ -677,6 +938,7 @@ def reject_candidate_evidence(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}" "/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-abilities/{candidate_ability_id}", "/candidate-abilities/{candidate_ability_id}",
tags=["review"], tags=["review"],
response_model=CandidateGraphResponse,
) )
def edit_candidate_ability( def edit_candidate_ability(
repository_id: int, repository_id: int,
@@ -702,6 +964,7 @@ def edit_candidate_ability(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}" "/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-capabilities/{candidate_capability_id}", "/candidate-capabilities/{candidate_capability_id}",
tags=["review"], tags=["review"],
response_model=CandidateGraphResponse,
) )
def edit_candidate_capability( def edit_candidate_capability(
repository_id: int, repository_id: int,
@@ -727,6 +990,7 @@ def edit_candidate_capability(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}" "/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-capabilities/{candidate_capability_id}/relink", "/candidate-capabilities/{candidate_capability_id}/relink",
tags=["review"], tags=["review"],
response_model=CandidateGraphResponse,
) )
def relink_candidate_capability( def relink_candidate_capability(
repository_id: int, repository_id: int,
@@ -752,6 +1016,7 @@ def relink_candidate_capability(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}" "/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-features/{candidate_feature_id}/relink", "/candidate-features/{candidate_feature_id}/relink",
tags=["review"], tags=["review"],
response_model=CandidateGraphResponse,
) )
def relink_candidate_feature( def relink_candidate_feature(
repository_id: int, repository_id: int,
@@ -777,6 +1042,7 @@ def relink_candidate_feature(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}" "/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-evidence/{candidate_evidence_id}/relink", "/candidate-evidence/{candidate_evidence_id}/relink",
tags=["review"], tags=["review"],
response_model=CandidateGraphResponse,
) )
def relink_candidate_evidence( def relink_candidate_evidence(
repository_id: int, repository_id: int,
@@ -802,6 +1068,7 @@ def relink_candidate_evidence(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}" "/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-abilities/{source_ability_id}/merge", "/candidate-abilities/{source_ability_id}/merge",
tags=["review"], tags=["review"],
response_model=CandidateGraphResponse,
) )
def merge_candidate_ability( def merge_candidate_ability(
repository_id: int, repository_id: int,
@@ -827,6 +1094,7 @@ def merge_candidate_ability(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}" "/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-capabilities/{source_capability_id}/merge", "/candidate-capabilities/{source_capability_id}/merge",
tags=["review"], tags=["review"],
response_model=CandidateGraphResponse,
) )
def merge_candidate_capability( def merge_candidate_capability(
repository_id: int, repository_id: int,
@@ -852,6 +1120,7 @@ def merge_candidate_capability(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}" "/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-features/{source_feature_id}/merge", "/candidate-features/{source_feature_id}/merge",
tags=["review"], tags=["review"],
response_model=CandidateGraphResponse,
) )
def merge_candidate_feature( def merge_candidate_feature(
repository_id: int, repository_id: int,
@@ -877,6 +1146,7 @@ def merge_candidate_feature(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}" "/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-evidence/{source_evidence_id}/merge", "/candidate-evidence/{source_evidence_id}/merge",
tags=["review"], tags=["review"],
response_model=CandidateGraphResponse,
) )
def merge_candidate_evidence( def merge_candidate_evidence(
repository_id: int, repository_id: int,
@@ -898,7 +1168,12 @@ def merge_candidate_evidence(
raise HTTPException(status_code=400, detail=str(exc)) from exc raise HTTPException(status_code=400, detail=str(exc)) from exc
@app.post("/repos/{repository_id}/abilities", status_code=201, tags=["registry"]) @app.post(
"/repos/{repository_id}/abilities",
status_code=201,
tags=["registry"],
response_model=IdResponse,
)
def create_ability( def create_ability(
repository_id: int, repository_id: int,
payload: AbilityCreate, payload: AbilityCreate,
@@ -911,7 +1186,11 @@ def create_ability(
return {"id": ability_id} return {"id": ability_id}
@app.patch("/repos/{repository_id}/abilities/{ability_id}", tags=["registry"]) @app.patch(
"/repos/{repository_id}/abilities/{ability_id}",
tags=["registry"],
response_model=RepositoryAbilityMapResponse,
)
def update_ability( def update_ability(
repository_id: int, repository_id: int,
ability_id: int, ability_id: int,
@@ -930,7 +1209,11 @@ def update_ability(
raise HTTPException(status_code=404, detail=str(exc)) from exc raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.delete("/repos/{repository_id}/abilities/{ability_id}", tags=["registry"]) @app.delete(
"/repos/{repository_id}/abilities/{ability_id}",
tags=["registry"],
response_model=RepositoryAbilityMapResponse,
)
def delete_ability( def delete_ability(
repository_id: int, repository_id: int,
ability_id: int, ability_id: int,
@@ -942,7 +1225,12 @@ def delete_ability(
raise HTTPException(status_code=404, detail=str(exc)) from exc raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.post("/repos/{repository_id}/capabilities", status_code=201, tags=["registry"]) @app.post(
"/repos/{repository_id}/capabilities",
status_code=201,
tags=["registry"],
response_model=IdResponse,
)
def create_capability( def create_capability(
repository_id: int, repository_id: int,
payload: CapabilityCreate, payload: CapabilityCreate,
@@ -955,7 +1243,11 @@ def create_capability(
return {"id": capability_id} return {"id": capability_id}
@app.patch("/repos/{repository_id}/capabilities/{capability_id}", tags=["registry"]) @app.patch(
"/repos/{repository_id}/capabilities/{capability_id}",
tags=["registry"],
response_model=RepositoryAbilityMapResponse,
)
def update_capability( def update_capability(
repository_id: int, repository_id: int,
capability_id: int, capability_id: int,
@@ -974,7 +1266,11 @@ def update_capability(
raise HTTPException(status_code=404, detail=str(exc)) from exc raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.delete("/repos/{repository_id}/capabilities/{capability_id}", tags=["registry"]) @app.delete(
"/repos/{repository_id}/capabilities/{capability_id}",
tags=["registry"],
response_model=RepositoryAbilityMapResponse,
)
def delete_capability( def delete_capability(
repository_id: int, repository_id: int,
capability_id: int, capability_id: int,
@@ -986,7 +1282,12 @@ def delete_capability(
raise HTTPException(status_code=404, detail=str(exc)) from exc raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.post("/repos/{repository_id}/features", status_code=201, tags=["registry"]) @app.post(
"/repos/{repository_id}/features",
status_code=201,
tags=["registry"],
response_model=IdResponse,
)
def create_feature( def create_feature(
repository_id: int, repository_id: int,
payload: FeatureCreate, payload: FeatureCreate,
@@ -999,7 +1300,11 @@ def create_feature(
return {"id": feature_id} return {"id": feature_id}
@app.patch("/repos/{repository_id}/features/{feature_id}", tags=["registry"]) @app.patch(
"/repos/{repository_id}/features/{feature_id}",
tags=["registry"],
response_model=RepositoryAbilityMapResponse,
)
def update_feature( def update_feature(
repository_id: int, repository_id: int,
feature_id: int, feature_id: int,
@@ -1018,7 +1323,11 @@ def update_feature(
raise HTTPException(status_code=404, detail=str(exc)) from exc raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.delete("/repos/{repository_id}/features/{feature_id}", tags=["registry"]) @app.delete(
"/repos/{repository_id}/features/{feature_id}",
tags=["registry"],
response_model=RepositoryAbilityMapResponse,
)
def delete_feature( def delete_feature(
repository_id: int, repository_id: int,
feature_id: int, feature_id: int,
@@ -1030,7 +1339,12 @@ def delete_feature(
raise HTTPException(status_code=404, detail=str(exc)) from exc raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.post("/repos/{repository_id}/evidence", status_code=201, tags=["registry"]) @app.post(
"/repos/{repository_id}/evidence",
status_code=201,
tags=["registry"],
response_model=IdResponse,
)
def create_evidence( def create_evidence(
repository_id: int, repository_id: int,
payload: EvidenceCreate, payload: EvidenceCreate,
@@ -1043,7 +1357,11 @@ def create_evidence(
return {"id": evidence_id} return {"id": evidence_id}
@app.patch("/repos/{repository_id}/evidence/{evidence_id}", tags=["registry"]) @app.patch(
"/repos/{repository_id}/evidence/{evidence_id}",
tags=["registry"],
response_model=RepositoryAbilityMapResponse,
)
def update_evidence( def update_evidence(
repository_id: int, repository_id: int,
evidence_id: int, evidence_id: int,
@@ -1062,7 +1380,11 @@ def update_evidence(
raise HTTPException(status_code=404, detail=str(exc)) from exc raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.delete("/repos/{repository_id}/evidence/{evidence_id}", tags=["registry"]) @app.delete(
"/repos/{repository_id}/evidence/{evidence_id}",
tags=["registry"],
response_model=RepositoryAbilityMapResponse,
)
def delete_evidence( def delete_evidence(
repository_id: int, repository_id: int,
evidence_id: int, evidence_id: int,
@@ -1074,7 +1396,11 @@ def delete_evidence(
raise HTTPException(status_code=404, detail=str(exc)) from exc raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/repos/{repository_id}/ability-map", tags=["registry"]) @app.get(
"/repos/{repository_id}/ability-map",
tags=["registry"],
response_model=RepositoryAbilityMapResponse,
)
def get_ability_map( def get_ability_map(
repository_id: int, repository_id: int,
service: RegistryService = Depends(get_service), service: RegistryService = Depends(get_service),
@@ -1085,7 +1411,7 @@ def get_ability_map(
raise HTTPException(status_code=404, detail=str(exc)) from exc raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/search", tags=["search"]) @app.get("/search", tags=["search"], response_model=list[SearchResultResponse])
def search( def search(
q: str = Query( q: str = Query(
..., ...,
@@ -1127,14 +1453,18 @@ def search(
] ]
@app.get("/abilities", tags=["search"]) @app.get("/abilities", tags=["search"], response_model=list[AbilitySummaryResponse])
def list_abilities( def list_abilities(
service: RegistryService = Depends(get_service), service: RegistryService = Depends(get_service),
) -> list[dict[str, object]]: ) -> list[dict[str, object]]:
return [asdict(ability) for ability in service.list_abilities()] return [asdict(ability) for ability in service.list_abilities()]
@app.get("/capabilities", tags=["search"]) @app.get(
"/capabilities",
tags=["search"],
response_model=list[CapabilitySummaryResponse],
)
def list_capabilities( def list_capabilities(
service: RegistryService = Depends(get_service), service: RegistryService = Depends(get_service),
) -> list[dict[str, object]]: ) -> list[dict[str, object]]:

View File

@@ -20,10 +20,18 @@ def test_openapi_groups_agent_facing_endpoints():
} }
search_operation = schema["paths"]["/search"]["get"] search_operation = schema["paths"]["/search"]["get"]
assert search_operation["tags"] == ["search"] assert search_operation["tags"] == ["search"]
search_response = search_operation["responses"]["200"]["content"][
"application/json"
]["schema"]
assert search_response["items"]["$ref"].endswith("/SearchResultResponse")
assert { assert {
parameter["name"]: parameter["description"] parameter["name"]: parameter["description"]
for parameter in search_operation["parameters"] for parameter in search_operation["parameters"]
}["q"].startswith("Natural-language") }["q"].startswith("Natural-language")
ability_map_response = schema["paths"]["/repos/{repository_id}/ability-map"][
"get"
]["responses"]["200"]["content"]["application/json"]["schema"]
assert ability_map_response["$ref"].endswith("/RepositoryAbilityMapResponse")
def test_api_manual_registry_loop(tmp_path): def test_api_manual_registry_loop(tmp_path):