confidence labels

This commit is contained in:
2026-04-26 08:45:00 +02:00
parent 2403accd06
commit 70feabe965
6 changed files with 194 additions and 61 deletions

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
from dataclasses import asdict
from pathlib import Path
from fastapi import Depends, FastAPI, HTTPException
from fastapi import Depends, FastAPI, HTTPException, Query
from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings, SettingsConfigDict
@@ -324,7 +324,26 @@ class CandidateEvidenceMerge(BaseModel):
}
app = FastAPI(title="Repository Ability Registry", version="0.1.0")
API_DESCRIPTION = (
"Register repositories, analyze their observable implementation facts, "
"curate reviewable ability graphs, and search approved repository abilities."
)
OPENAPI_TAGS = [
{"name": "health", "description": "Service health checks."},
{"name": "repositories", "description": "Repository registration and metadata."},
{"name": "analysis", "description": "Repository scans and extracted review inputs."},
{"name": "review", "description": "Candidate graph approval and correction workflow."},
{"name": "registry", "description": "Approved ability maps and manual registry CRUD."},
{"name": "search", "description": "Agent-facing discovery endpoints."},
]
app = FastAPI(
title="Repository Ability Registry",
version="0.1.0",
description=API_DESCRIPTION,
openapi_tags=OPENAPI_TAGS,
)
from repo_registry.web_ui.views import router as ui_router
@@ -332,12 +351,12 @@ from repo_registry.web_ui.views import router as ui_router
app.include_router(ui_router)
@app.get("/health")
@app.get("/health", tags=["health"])
def health() -> dict[str, str]:
return {"status": "ok"}
@app.post("/repos", status_code=201)
@app.post("/repos", status_code=201, tags=["repositories"])
def create_repository(
payload: RepositoryCreate,
service: RegistryService = Depends(get_service),
@@ -349,14 +368,14 @@ def create_repository(
return asdict(repository)
@app.get("/repos")
@app.get("/repos", tags=["repositories"])
def list_repositories(
service: RegistryService = Depends(get_service),
) -> list[dict[str, object]]:
return [asdict(repository) for repository in service.list_repositories()]
@app.get("/repos/{repository_id}")
@app.get("/repos/{repository_id}", tags=["repositories"])
def get_repository(
repository_id: int,
service: RegistryService = Depends(get_service),
@@ -367,7 +386,7 @@ def get_repository(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.patch("/repos/{repository_id}")
@app.patch("/repos/{repository_id}", tags=["repositories"])
def update_repository(
repository_id: int,
payload: RepositoryUpdate,
@@ -384,7 +403,7 @@ def update_repository(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.delete("/repos/{repository_id}", status_code=204)
@app.delete("/repos/{repository_id}", status_code=204, tags=["repositories"])
def delete_repository(
repository_id: int,
service: RegistryService = Depends(get_service),
@@ -395,7 +414,7 @@ def delete_repository(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.post("/repos/{repository_id}/analysis-runs", status_code=201)
@app.post("/repos/{repository_id}/analysis-runs", status_code=201, tags=["analysis"])
def create_analysis_run(
repository_id: int,
payload: AnalysisRunCreate,
@@ -411,7 +430,7 @@ def create_analysis_run(
return asdict(summary)
@app.get("/repos/{repository_id}/analysis-runs")
@app.get("/repos/{repository_id}/analysis-runs", tags=["analysis"])
def list_analysis_runs(
repository_id: int,
service: RegistryService = Depends(get_service),
@@ -422,7 +441,7 @@ def list_analysis_runs(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/repos/{repository_id}/analysis-runs/{analysis_run_id}")
@app.get("/repos/{repository_id}/analysis-runs/{analysis_run_id}", tags=["analysis"])
def get_analysis_run(
repository_id: int,
analysis_run_id: int,
@@ -434,7 +453,7 @@ def get_analysis_run(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/repos/{repository_id}/review-decisions")
@app.get("/repos/{repository_id}/review-decisions", tags=["review"])
def list_repository_review_decisions(
repository_id: int,
service: RegistryService = Depends(get_service),
@@ -448,7 +467,10 @@ def list_repository_review_decisions(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/repos/{repository_id}/analysis-runs/{analysis_run_id}/review-decisions")
@app.get(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}/review-decisions",
tags=["review"],
)
def list_analysis_run_review_decisions(
repository_id: int,
analysis_run_id: int,
@@ -466,7 +488,7 @@ def list_analysis_run_review_decisions(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/repos/{repository_id}/observed-facts")
@app.get("/repos/{repository_id}/observed-facts", tags=["analysis"])
def list_observed_facts(
repository_id: int,
analysis_run_id: int | None = None,
@@ -481,7 +503,7 @@ def list_observed_facts(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/repos/{repository_id}/content-chunks")
@app.get("/repos/{repository_id}/content-chunks", tags=["analysis"])
def list_content_chunks(
repository_id: int,
analysis_run_id: int | None = None,
@@ -496,7 +518,10 @@ def list_content_chunks(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/repos/{repository_id}/analysis-runs/{analysis_run_id}/content-chunks")
@app.get(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}/content-chunks",
tags=["analysis"],
)
def list_analysis_run_content_chunks(
repository_id: int,
analysis_run_id: int,
@@ -511,7 +536,10 @@ def list_analysis_run_content_chunks(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/repos/{repository_id}/analysis-runs/{analysis_run_id}/candidate-graph")
@app.get(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}/candidate-graph",
tags=["review"],
)
def get_candidate_graph(
repository_id: int,
analysis_run_id: int,
@@ -523,7 +551,10 @@ def get_candidate_graph(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.post("/repos/{repository_id}/analysis-runs/{analysis_run_id}/candidate-graph/approve")
@app.post(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}/candidate-graph/approve",
tags=["review"],
)
def approve_candidate_graph(
repository_id: int,
analysis_run_id: int,
@@ -544,7 +575,8 @@ def approve_candidate_graph(
@app.post(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-abilities/{candidate_ability_id}/reject"
"/candidate-abilities/{candidate_ability_id}/reject",
tags=["review"],
)
def reject_candidate_ability(
repository_id: int,
@@ -568,7 +600,8 @@ def reject_candidate_ability(
@app.post(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-capabilities/{candidate_capability_id}/reject"
"/candidate-capabilities/{candidate_capability_id}/reject",
tags=["review"],
)
def reject_candidate_capability(
repository_id: int,
@@ -592,7 +625,8 @@ def reject_candidate_capability(
@app.post(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-features/{candidate_feature_id}/reject"
"/candidate-features/{candidate_feature_id}/reject",
tags=["review"],
)
def reject_candidate_feature(
repository_id: int,
@@ -616,7 +650,8 @@ def reject_candidate_feature(
@app.post(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-evidence/{candidate_evidence_id}/reject"
"/candidate-evidence/{candidate_evidence_id}/reject",
tags=["review"],
)
def reject_candidate_evidence(
repository_id: int,
@@ -640,7 +675,8 @@ def reject_candidate_evidence(
@app.patch(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-abilities/{candidate_ability_id}"
"/candidate-abilities/{candidate_ability_id}",
tags=["review"],
)
def edit_candidate_ability(
repository_id: int,
@@ -664,7 +700,8 @@ def edit_candidate_ability(
@app.patch(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-capabilities/{candidate_capability_id}"
"/candidate-capabilities/{candidate_capability_id}",
tags=["review"],
)
def edit_candidate_capability(
repository_id: int,
@@ -688,7 +725,8 @@ def edit_candidate_capability(
@app.post(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-capabilities/{candidate_capability_id}/relink"
"/candidate-capabilities/{candidate_capability_id}/relink",
tags=["review"],
)
def relink_candidate_capability(
repository_id: int,
@@ -712,7 +750,8 @@ def relink_candidate_capability(
@app.post(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-features/{candidate_feature_id}/relink"
"/candidate-features/{candidate_feature_id}/relink",
tags=["review"],
)
def relink_candidate_feature(
repository_id: int,
@@ -736,7 +775,8 @@ def relink_candidate_feature(
@app.post(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-evidence/{candidate_evidence_id}/relink"
"/candidate-evidence/{candidate_evidence_id}/relink",
tags=["review"],
)
def relink_candidate_evidence(
repository_id: int,
@@ -760,7 +800,8 @@ def relink_candidate_evidence(
@app.post(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-abilities/{source_ability_id}/merge"
"/candidate-abilities/{source_ability_id}/merge",
tags=["review"],
)
def merge_candidate_ability(
repository_id: int,
@@ -784,7 +825,8 @@ def merge_candidate_ability(
@app.post(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-capabilities/{source_capability_id}/merge"
"/candidate-capabilities/{source_capability_id}/merge",
tags=["review"],
)
def merge_candidate_capability(
repository_id: int,
@@ -808,7 +850,8 @@ def merge_candidate_capability(
@app.post(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-features/{source_feature_id}/merge"
"/candidate-features/{source_feature_id}/merge",
tags=["review"],
)
def merge_candidate_feature(
repository_id: int,
@@ -832,7 +875,8 @@ def merge_candidate_feature(
@app.post(
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
"/candidate-evidence/{source_evidence_id}/merge"
"/candidate-evidence/{source_evidence_id}/merge",
tags=["review"],
)
def merge_candidate_evidence(
repository_id: int,
@@ -854,7 +898,7 @@ def merge_candidate_evidence(
raise HTTPException(status_code=400, detail=str(exc)) from exc
@app.post("/repos/{repository_id}/abilities", status_code=201)
@app.post("/repos/{repository_id}/abilities", status_code=201, tags=["registry"])
def create_ability(
repository_id: int,
payload: AbilityCreate,
@@ -867,7 +911,7 @@ def create_ability(
return {"id": ability_id}
@app.patch("/repos/{repository_id}/abilities/{ability_id}")
@app.patch("/repos/{repository_id}/abilities/{ability_id}", tags=["registry"])
def update_ability(
repository_id: int,
ability_id: int,
@@ -886,7 +930,7 @@ def update_ability(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.delete("/repos/{repository_id}/abilities/{ability_id}")
@app.delete("/repos/{repository_id}/abilities/{ability_id}", tags=["registry"])
def delete_ability(
repository_id: int,
ability_id: int,
@@ -898,7 +942,7 @@ def delete_ability(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.post("/repos/{repository_id}/capabilities", status_code=201)
@app.post("/repos/{repository_id}/capabilities", status_code=201, tags=["registry"])
def create_capability(
repository_id: int,
payload: CapabilityCreate,
@@ -911,7 +955,7 @@ def create_capability(
return {"id": capability_id}
@app.patch("/repos/{repository_id}/capabilities/{capability_id}")
@app.patch("/repos/{repository_id}/capabilities/{capability_id}", tags=["registry"])
def update_capability(
repository_id: int,
capability_id: int,
@@ -930,7 +974,7 @@ def update_capability(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.delete("/repos/{repository_id}/capabilities/{capability_id}")
@app.delete("/repos/{repository_id}/capabilities/{capability_id}", tags=["registry"])
def delete_capability(
repository_id: int,
capability_id: int,
@@ -942,7 +986,7 @@ def delete_capability(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.post("/repos/{repository_id}/features", status_code=201)
@app.post("/repos/{repository_id}/features", status_code=201, tags=["registry"])
def create_feature(
repository_id: int,
payload: FeatureCreate,
@@ -955,7 +999,7 @@ def create_feature(
return {"id": feature_id}
@app.patch("/repos/{repository_id}/features/{feature_id}")
@app.patch("/repos/{repository_id}/features/{feature_id}", tags=["registry"])
def update_feature(
repository_id: int,
feature_id: int,
@@ -974,7 +1018,7 @@ def update_feature(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.delete("/repos/{repository_id}/features/{feature_id}")
@app.delete("/repos/{repository_id}/features/{feature_id}", tags=["registry"])
def delete_feature(
repository_id: int,
feature_id: int,
@@ -986,7 +1030,7 @@ def delete_feature(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.post("/repos/{repository_id}/evidence", status_code=201)
@app.post("/repos/{repository_id}/evidence", status_code=201, tags=["registry"])
def create_evidence(
repository_id: int,
payload: EvidenceCreate,
@@ -999,7 +1043,7 @@ def create_evidence(
return {"id": evidence_id}
@app.patch("/repos/{repository_id}/evidence/{evidence_id}")
@app.patch("/repos/{repository_id}/evidence/{evidence_id}", tags=["registry"])
def update_evidence(
repository_id: int,
evidence_id: int,
@@ -1018,7 +1062,7 @@ def update_evidence(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.delete("/repos/{repository_id}/evidence/{evidence_id}")
@app.delete("/repos/{repository_id}/evidence/{evidence_id}", tags=["registry"])
def delete_evidence(
repository_id: int,
evidence_id: int,
@@ -1030,7 +1074,7 @@ def delete_evidence(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/repos/{repository_id}/ability-map")
@app.get("/repos/{repository_id}/ability-map", tags=["registry"])
def get_ability_map(
repository_id: int,
service: RegistryService = Depends(get_service),
@@ -1041,14 +1085,33 @@ def get_ability_map(
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/search")
@app.get("/search", tags=["search"])
def search(
q: str,
status: str | None = None,
language: str | None = None,
framework: str | None = None,
ability: str | None = None,
capability: str | None = None,
q: str = Query(
...,
description="Natural-language or keyword query over approved registry entries.",
examples=["classify email", "FastAPI health endpoint"],
),
status: str | None = Query(
default=None,
description="Filter repositories by registry status, such as indexed.",
),
language: str | None = Query(
default=None,
description="Filter by observed programming language.",
),
framework: str | None = Query(
default=None,
description="Filter by observed framework hint.",
),
ability: str | None = Query(
default=None,
description="Filter to results under an approved ability name.",
),
capability: str | None = Query(
default=None,
description="Filter to results under an approved capability name.",
),
service: RegistryService = Depends(get_service),
) -> list[dict[str, object]]:
return [
@@ -1064,14 +1127,14 @@ def search(
]
@app.get("/abilities")
@app.get("/abilities", tags=["search"])
def list_abilities(
service: RegistryService = Depends(get_service),
) -> list[dict[str, object]]:
return [asdict(ability) for ability in service.list_abilities()]
@app.get("/capabilities")
@app.get("/capabilities", tags=["search"])
def list_capabilities(
service: RegistryService = Depends(get_service),
) -> list[dict[str, object]]: