Status drift warnings

This commit is contained in:
2026-05-02 11:54:07 +02:00
parent 06ed12b2c7
commit 010d23bc38
10 changed files with 538 additions and 8 deletions

View File

@@ -372,6 +372,7 @@ def test_candidate_generator_maps_llm_provider_facts_to_capability():
feature for feature in capability.features if feature.name == "Use OpenRouter Models"
)
assert openrouter_feature.primary_class == "integration"
assert {"llm-provider", "openrouter"} <= set(openrouter_feature.attributes)
def test_candidate_generator_does_not_promote_llm_provider_mentions_to_capability():
@@ -402,4 +403,3 @@ def test_candidate_generator_does_not_promote_llm_provider_mentions_to_capabilit
for capability in graph[0].capabilities
if capability.name == "Route LLM Requests Across Providers"
] == []
assert {"llm-provider", "openrouter"} <= set(openrouter_feature.attributes)

View File

@@ -746,10 +746,92 @@ def test_analyze_repository_can_trusted_auto_approve_candidates(tmp_path):
decisions = service.list_review_decisions(repository.id, summary.analysis_run.id)
assert service.get_repository(repository.id).status == "indexed"
assert graph.abilities[0].status == "approved"
statuses_by_capability = {
capability.name: capability.status
for capability in graph.abilities[0].capabilities
}
assert statuses_by_capability["Expose Repository Interface"] == "approved"
assert statuses_by_capability["Describe Repository Structure"] == "candidate"
assert ability_map.abilities[0].name == "Report Health Over HTTP"
assert decisions[0].action == "trusted_auto_approve_candidate_graph"
assert "deterministic candidate generation" in decisions[0].notes
assert "Auto-approved 1 safe candidate capability(s); left 1 for review." in decisions[0].notes
def test_rebuild_characteristics_dry_run_preserves_approved_map(tmp_path):
source = tmp_path / "repo"
source.mkdir()
(source / "README.md").write_text("# Rebuild\nReports health over HTTP.\n", encoding="utf-8")
(source / "app.py").write_text('@app.get("/health")\ndef health():\n return {}\n', encoding="utf-8")
service = make_service(tmp_path)
repository = service.register_repository(name="Rebuild", url=str(source))
summary = service.analyze_repository(repository.id, use_llm_assistance=False)
service.approve_candidate_graph(repository.id, summary.analysis_run.id)
result = service.rebuild_characteristics_from_scratch(
repository.id,
dry_run=True,
source_path=str(source),
use_llm_assistance=False,
)
assert result.dry_run is True
assert result.cleared_approved is False
assert result.previous_counts["abilities"] == 1
assert result.previous_ids["abilities"]
assert result.candidate_counts["abilities"] == 1
assert service.ability_map(repository.id).abilities
decisions = service.list_review_decisions(repository.id, result.analysis_run.id)
assert decisions[-1].action == "dry_run_rebuild_characteristics_from_scratch"
def test_rebuild_characteristics_requires_confirmation_before_clearing(tmp_path):
service = make_service(tmp_path)
source = tmp_path / "repo"
source.mkdir()
(source / "README.md").write_text("# Rebuild\n", encoding="utf-8")
repository = service.register_repository(name="Rebuild", url=str(source))
try:
service.rebuild_characteristics_from_scratch(
repository.id,
dry_run=False,
confirm=False,
source_path=str(source),
use_llm_assistance=False,
)
except ValueError as exc:
assert "confirm=True" in str(exc)
else:
raise AssertionError("expected confirmed rebuild to require confirm=True")
def test_rebuild_characteristics_confirmed_clears_approved_map(tmp_path):
source = tmp_path / "repo"
source.mkdir()
(source / "README.md").write_text("# Rebuild\nReports health over HTTP.\n", encoding="utf-8")
(source / "app.py").write_text('@app.get("/health")\ndef health():\n return {}\n', encoding="utf-8")
service = make_service(tmp_path)
repository = service.register_repository(name="Rebuild", url=str(source))
summary = service.analyze_repository(repository.id, use_llm_assistance=False)
service.approve_candidate_graph(repository.id, summary.analysis_run.id)
result = service.rebuild_characteristics_from_scratch(
repository.id,
dry_run=False,
confirm=True,
source_path=str(source),
use_llm_assistance=False,
)
assert result.cleared_approved is True
assert result.previous_counts["abilities"] == 1
assert result.previous_ids["abilities"]
assert service.ability_map(repository.id).abilities == []
assert service.get_repository(repository.id).status == "analyzed"
decisions = service.list_review_decisions(repository.id, result.analysis_run.id)
assert decisions[-1].action == "rebuild_characteristics_from_scratch"
assert "Previous approved IDs" in decisions[-1].notes
def test_analyze_repository_records_llm_failure_and_falls_back(tmp_path):

View File

@@ -182,6 +182,12 @@ def test_openapi_contract_snapshot_for_stable_agent_paths():
"/repos/{repository_id}/analysis-runs/{base_analysis_run_id}/diff/{target_analysis_run_id}": {
"get": {"tags": ["review"], "success_schema": "AnalysisRunDiffResponse"}
},
"/repos/{repository_id}/characteristics/rebuild": {
"post": {
"tags": ["analysis"],
"success_schema": "CharacteristicRebuildResponse",
}
},
"/repos/{repository_id}/analysis-runs/{analysis_run_id}/candidate-abilities/{candidate_ability_id}": {
"patch": {"tags": ["review"], "success_schema": "CandidateGraphResponse"}
},
@@ -1767,7 +1773,118 @@ def test_ui_register_and_explore_lands_on_analysis_result(tmp_path):
repository_detail = client.get("/ui/repos/1")
assert repository_detail.status_code == 200
assert "Use Approved Registry" in repository_detail.text
assert "Latest Candidate Graph" in repository_detail.text
assert "Rebuild Characteristics" in repository_detail.text
assert "Use Approved Registry" not in repository_detail.text
finally:
app.dependency_overrides.clear()
def test_rebuild_characteristics_endpoint_dry_run_and_confirm(tmp_path):
source = tmp_path / "rebuild-api"
source.mkdir()
(source / "README.md").write_text("# Rebuild API\nReports status.\n", encoding="utf-8")
(source / "app.py").write_text('@app.get("/status")\ndef status():\n return {}\n', encoding="utf-8")
def override_settings():
return Settings(
database_path=str(tmp_path / "rebuild-api.sqlite3"),
checkout_root=str(tmp_path / "rebuild-api-checkouts"),
)
app.dependency_overrides[get_settings] = override_settings
client = TestClient(app)
try:
repository_id = client.post(
"/repos",
json={"name": "Rebuild API", "url": str(source)},
).json()["id"]
run_id = client.post(
f"/repos/{repository_id}/analysis-runs",
json={"source_path": str(source), "use_llm_assistance": False},
).json()["analysis_run"]["id"]
approve_response = client.post(
f"/repos/{repository_id}/analysis-runs/{run_id}/candidate-graph/approve",
json={"notes": "approve before rebuild"},
)
assert approve_response.status_code == 200
dry_run_response = client.post(
f"/repos/{repository_id}/characteristics/rebuild",
json={
"dry_run": True,
"source_path": str(source),
"use_llm_assistance": False,
},
)
assert dry_run_response.status_code == 200
dry_run = dry_run_response.json()
assert dry_run["cleared_approved"] is False
assert dry_run["previous_counts"]["abilities"] == 1
assert dry_run["previous_ids"]["abilities"]
rejected_response = client.post(
f"/repos/{repository_id}/characteristics/rebuild",
json={
"dry_run": False,
"confirm": False,
"source_path": str(source),
"use_llm_assistance": False,
},
)
assert rejected_response.status_code == 400
confirmed_response = client.post(
f"/repos/{repository_id}/characteristics/rebuild",
json={
"dry_run": False,
"confirm": True,
"source_path": str(source),
"use_llm_assistance": False,
},
)
assert confirmed_response.status_code == 200
confirmed = confirmed_response.json()
assert confirmed["cleared_approved"] is True
assert client.get(f"/repos/{repository_id}/ability-map").json()["abilities"] == []
finally:
app.dependency_overrides.clear()
def test_ui_rebuild_characteristics_form_runs_dry_run(tmp_path):
source = tmp_path / "ui-rebuild"
source.mkdir()
(source / "README.md").write_text("# UI Rebuild\nReports status.\n", encoding="utf-8")
def override_settings():
return Settings(
database_path=str(tmp_path / "ui-rebuild.sqlite3"),
checkout_root=str(tmp_path / "ui-rebuild-checkouts"),
)
app.dependency_overrides[get_settings] = override_settings
client = TestClient(app)
try:
repository_id = client.post(
"/repos",
json={"name": "UI Rebuild", "url": str(source)},
).json()["id"]
response = client.post(
f"/ui/repos/{repository_id}/characteristics/rebuild",
data={
"source_path": str(source),
"dry_run": "1",
"use_llm_assistance": "",
"use_cached_checkout": "",
},
follow_redirects=False,
)
assert response.status_code == 303
assert f"/ui/repos/{repository_id}/analysis-runs/" in response.headers["location"]
detail = client.get(response.headers["location"])
assert "dry_run_rebuild_characteristics_from_scratch" in detail.text
finally:
app.dependency_overrides.clear()