feat(governance): batch queue stale km reviews
All checks were successful
CD Pipeline / tests (push) Successful in 5m47s
Code Review / ai-code-review (push) Successful in 11s
Type Sync Check / check-type-sync (push) Successful in 27s
CD Pipeline / build-and-deploy (push) Successful in 4m13s
CD Pipeline / post-deploy-checks (push) Successful in 2m11s

This commit is contained in:
Your Name
2026-05-24 20:47:31 +08:00
parent fb40b8f469
commit 943093a49b
7 changed files with 1078 additions and 1 deletions

View File

@@ -37,6 +37,9 @@ from src.models.governance import (
KnowledgeReviewDraftStaleRatioSnapshot,
KnowledgeStaleCandidate,
KnowledgeStaleCandidatesResponse,
KnowledgeStaleOwnerReviewBatchItem,
KnowledgeStaleOwnerReviewBatchQueueRequest,
KnowledgeStaleOwnerReviewBatchQueueResponse,
KnowledgeStaleOwnerReviewCompleteRequest,
KnowledgeStaleOwnerReviewCompleteResponse,
KnowledgeStaleOwnerReviewRequest,
@@ -53,6 +56,8 @@ from src.services.governance_km_review_service import (
)
from src.services.governance_km_stale_review_service import (
KmStaleOwnerReviewError,
_build_batch_owner_review_decision_context,
_build_batch_queue_plan_fingerprint,
_build_completion_plan_fingerprint,
_build_owner_review_completion_audit_context,
_build_stale_owner_review_decision_context,
@@ -765,6 +770,163 @@ class TestKmReviewDraftDedupe:
assert r.status_code == 409
assert r.json()["detail"] == "KM entry is no longer past the stale threshold"
def test_batch_queue_stale_candidate_endpoint_returns_batch_dispatch(self, client):
"""P0/P1 stale KM batch queue 應只寫治理 dispatch不改寫 KM。"""
fake = KnowledgeStaleOwnerReviewBatchQueueResponse(
project_id="awoooi",
status="queued",
owner="operator_console",
dry_run=False,
priority_tiers=["P0", "P1"],
requested_limit=10,
candidate_count=2,
queued_count=1,
already_queued_count=1,
skipped_count=0,
batch_governance_event_id="event-batch-001",
batch_dispatch_id="dispatch-batch-001",
workflow_stage="batch_owner_review_queued",
writes_km=False,
writes_governance_audit=True,
stale_ratio_snapshot=KnowledgeReviewDraftStaleRatioSnapshot(
stale_count=119,
total_count=200,
stale_ratio=0.595,
threshold=0.2,
stale_days=7,
),
dry_run_plan_fingerprint="sha256:" + "b" * 64,
items=[
KnowledgeStaleOwnerReviewBatchItem(
entry_id="km-001",
title="Sentry checkout failure repair",
priority_tier="P0",
recommended_action="refresh_with_evidence",
status="queued",
governance_event_id="event-001",
dispatch_id="dispatch-001",
workflow_stage="waiting_owner_review",
),
KnowledgeStaleOwnerReviewBatchItem(
entry_id="km-002",
title="Already queued",
priority_tier="P1",
recommended_action="owner_review",
status="already_queued",
reason="active_owner_review_exists",
dispatch_id="dispatch-002",
workflow_stage="waiting_owner_review",
),
],
generated_at=NOW,
)
captured: dict = {}
async def mock_batch_queue(**kwargs):
captured.update(kwargs)
return fake
with patch(
"src.api.v1.ai_governance.batch_queue_km_stale_owner_reviews",
new=mock_batch_queue,
):
r = client.post(
"/api/v1/ai/governance/km-stale-candidates/batch-queue-review",
json={
"project_id": "awoooi",
"priority_tiers": ["P0", "P1"],
"limit": 10,
"owner": "operator_console",
"dry_run": False,
"dry_run_plan_fingerprint": "sha256:" + "b" * 64,
},
)
assert r.status_code == 200
assert isinstance(captured["request"], KnowledgeStaleOwnerReviewBatchQueueRequest)
data = r.json()
assert data["schema_version"] == "km_stale_owner_review_batch_v1"
assert data["status"] == "queued"
assert data["workflow_stage"] == "batch_owner_review_queued"
assert data["writes_km"] is False
assert data["writes_governance_audit"] is True
assert data["batch_dispatch_id"] == "dispatch-batch-001"
assert data["items"][0]["status"] == "queued"
assert data["items"][1]["status"] == "already_queued"
def test_batch_queue_stale_candidate_endpoint_maps_validation_error(self, client):
async def mock_batch_queue(**kwargs):
raise KmStaleOwnerReviewError(
409,
"dry_run_plan_fingerprint does not match the latest stale KM batch queue plan",
)
with patch(
"src.api.v1.ai_governance.batch_queue_km_stale_owner_reviews",
new=mock_batch_queue,
):
r = client.post(
"/api/v1/ai/governance/km-stale-candidates/batch-queue-review",
json={
"project_id": "awoooi",
"limit": 10,
"dry_run": False,
"dry_run_plan_fingerprint": "sha256:" + "c" * 64,
},
)
assert r.status_code == 409
assert "batch queue plan" in r.json()["detail"]
def test_stale_owner_review_batch_context_is_operator_visible(self):
request = KnowledgeStaleOwnerReviewBatchQueueRequest(
project_id="awoooi",
priority_tiers=["P0", "P1"],
limit=10,
owner="operator_console",
dry_run=False,
)
snapshot = KnowledgeReviewDraftStaleRatioSnapshot(
stale_count=119,
total_count=200,
stale_ratio=0.595,
threshold=0.2,
stale_days=7,
)
items = [
KnowledgeStaleOwnerReviewBatchItem(
entry_id="km-001",
title="Sentry checkout failure repair",
priority_tier="P0",
recommended_action="refresh_with_evidence",
status="would_queue",
workflow_stage="waiting_owner_review",
)
]
fingerprint = _build_batch_queue_plan_fingerprint(
request=request,
items=items,
snapshot=snapshot,
)
ctx = _build_batch_owner_review_decision_context(
batch_governance_event_id="event-batch-001",
batch_dispatch_id="dispatch-batch-001",
request=request,
items=items,
stale_ratio_snapshot=snapshot,
plan_fingerprint=fingerprint,
)
assert fingerprint.startswith("sha256:")
assert ctx["decision_path"] == "batch_owner_review_queued"
assert ctx["workflow"]["work_kind"] == "km_stale_owner_review_batch"
assert ctx["workflow"]["current_stage"] == "batch_owner_review_queued"
assert ctx["workflow"]["writes_km"] is False
assert ctx["workflow"]["writes_km_without_approval"] is False
assert ctx["worker_result"]["status"] == "batch_owner_review_queued"
assert ctx["worker_result"]["queued_count"] == 1
assert ctx["ownership"]["lead_agent"] == "Hermes"
def test_stale_owner_review_context_is_operator_visible(self):
candidate = {
"entry_id": "km-001",