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
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:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user