feat(governance): surface stale km priority queue
Some checks failed
CD Pipeline / tests (push) Successful in 5m29s
Code Review / ai-code-review (push) Successful in 11s
Type Sync Check / check-type-sync (push) Successful in 32s
CD Pipeline / build-and-deploy (push) Failing after 5m43s
CD Pipeline / post-deploy-checks (push) Has been skipped
Some checks failed
CD Pipeline / tests (push) Successful in 5m29s
Code Review / ai-code-review (push) Successful in 11s
Type Sync Check / check-type-sync (push) Successful in 32s
CD Pipeline / build-and-deploy (push) Failing after 5m43s
CD Pipeline / post-deploy-checks (push) Has been skipped
This commit is contained in:
@@ -22,6 +22,7 @@ from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from src.api.v1.ai_governance import router
|
||||
from src.db.models import KnowledgeEntryRecord
|
||||
from src.models.governance import (
|
||||
DailyCount,
|
||||
DispatchItem,
|
||||
@@ -34,8 +35,11 @@ from src.models.governance import (
|
||||
KnowledgeReviewDraftDedupeGroup,
|
||||
KnowledgeReviewDraftDedupeResponse,
|
||||
KnowledgeReviewDraftStaleRatioSnapshot,
|
||||
KnowledgeStaleCandidate,
|
||||
KnowledgeStaleCandidatesResponse,
|
||||
map_severity,
|
||||
)
|
||||
from src.models.knowledge import EntrySource, EntryStatus, EntryType
|
||||
from src.services.governance_km_review_service import (
|
||||
KmReviewDraftArchiveError,
|
||||
_build_dry_run_plan_fingerprint,
|
||||
@@ -45,6 +49,7 @@ from src.services.governance_km_review_service import (
|
||||
)
|
||||
from src.services.governance_query_service import (
|
||||
_build_km_review_draft_dedupe_groups,
|
||||
_build_km_stale_candidate,
|
||||
_extract_archived_count,
|
||||
_extract_dry_run_plan_fingerprint,
|
||||
_extract_governance_event_id_from_tags,
|
||||
@@ -593,6 +598,97 @@ class TestKmReviewDraftDedupe:
|
||||
assert first.archive_history[0].executor_type == "hermes_km_stale_ratio_recheck"
|
||||
assert first.archive_history[0].stale_ratio_snapshot["stale_ratio"] == pytest.approx(0.1)
|
||||
|
||||
def test_km_stale_candidates_endpoint_returns_read_only_priority_queue(self, client):
|
||||
"""stale KM endpoint 應回傳 owner 可排序處理的 read-only 清單。"""
|
||||
fake = KnowledgeStaleCandidatesResponse(
|
||||
project_id="awoooi",
|
||||
total_stale=1490,
|
||||
returned=1,
|
||||
threshold_days=7,
|
||||
items=[
|
||||
KnowledgeStaleCandidate(
|
||||
entry_id="km-001",
|
||||
project_id="awoooi",
|
||||
title="Sentry / SigNoz checkout repair runbook",
|
||||
entry_type="auto_runbook",
|
||||
category="AI系統",
|
||||
status="review",
|
||||
source="ai_extracted",
|
||||
updated_at=NOW - timedelta(days=21),
|
||||
stale_days=21,
|
||||
view_count=9,
|
||||
priority_score=265,
|
||||
priority_tier="P0",
|
||||
recommended_action="refresh_with_evidence",
|
||||
reasons=[
|
||||
"linked_incident",
|
||||
"linked_playbook",
|
||||
"sentry_context",
|
||||
"signoz_context",
|
||||
],
|
||||
correlation_sources=["incident", "playbook", "sentry", "signoz"],
|
||||
related_incident_id="INC-20260513-79ED5E",
|
||||
related_playbook_id="pb:auto-repair-canary",
|
||||
tags=["sentry", "signoz"],
|
||||
)
|
||||
],
|
||||
generated_at=NOW,
|
||||
)
|
||||
captured: dict = {}
|
||||
|
||||
async def mock_query(**kwargs):
|
||||
captured.update(kwargs)
|
||||
return fake
|
||||
|
||||
with patch("src.api.v1.ai_governance.query_km_stale_candidates", new=mock_query):
|
||||
r = client.get(
|
||||
"/api/v1/ai/governance/km-stale-candidates"
|
||||
"?project_id=awoooi&limit=25"
|
||||
)
|
||||
|
||||
assert r.status_code == 200
|
||||
assert captured == {"project_id": "awoooi", "limit": 25}
|
||||
data = r.json()
|
||||
assert data["writes_on_read"] is False
|
||||
assert data["manual_review_required"] is True
|
||||
assert data["total_stale"] == 1490
|
||||
assert data["items"][0]["priority_tier"] == "P0"
|
||||
assert data["items"][0]["correlation_sources"] == [
|
||||
"incident",
|
||||
"playbook",
|
||||
"sentry",
|
||||
"signoz",
|
||||
]
|
||||
|
||||
def test_build_km_stale_candidate_prioritizes_linked_evidence(self):
|
||||
"""有 Incident / PlayBook / Sentry / SigNoz 脈絡的 stale KM 應排前面。"""
|
||||
record = KnowledgeEntryRecord(
|
||||
id="km-001",
|
||||
project_id="awoooi",
|
||||
title="Sentry checkout failure repair",
|
||||
content="Use SigNoz trace and PlayBook verification before KM writeback.",
|
||||
entry_type=EntryType.AUTO_RUNBOOK,
|
||||
category="AI系統",
|
||||
tags=["sentry", "signoz", "workflow:kb_growth_healthcheck"],
|
||||
source=EntrySource.AI_EXTRACTED,
|
||||
status=EntryStatus.REVIEW,
|
||||
related_incident_id="INC-20260513-79ED5E",
|
||||
related_playbook_id="pb:auto-repair-canary",
|
||||
view_count=7,
|
||||
updated_at=NOW - timedelta(days=35),
|
||||
)
|
||||
|
||||
candidate = _build_km_stale_candidate(record, now=NOW, threshold_days=7)
|
||||
|
||||
assert candidate.priority_tier == "P0"
|
||||
assert candidate.recommended_action == "refresh_with_evidence"
|
||||
assert candidate.stale_days == 35
|
||||
assert candidate.correlation_sources == ["incident", "playbook", "sentry", "signoz"]
|
||||
assert "linked_incident" in candidate.reasons
|
||||
assert "linked_playbook" in candidate.reasons
|
||||
assert "sentry_context" in candidate.reasons
|
||||
assert "signoz_context" in candidate.reasons
|
||||
|
||||
def test_archive_endpoint_requires_owner_shape_and_returns_audit_result(self, client):
|
||||
"""Owner 批准後的 archive endpoint 應回傳 KM write 與 audit write 結果。"""
|
||||
fake = KnowledgeReviewDraftArchiveResponse(
|
||||
|
||||
Reference in New Issue
Block a user