fix(governance): intake km healthcheck dispatches
This commit is contained in:
@@ -235,14 +235,21 @@ class TestCheckKnowledgeDegradation:
|
||||
total_mock.scalar.return_value = 10
|
||||
stale_mock = MagicMock()
|
||||
stale_mock.scalar.return_value = 3
|
||||
insert_mock = MagicMock()
|
||||
|
||||
mock_db.execute = AsyncMock(side_effect=[total_mock, stale_mock])
|
||||
mock_db.execute = AsyncMock(side_effect=[total_mock, stale_mock, insert_mock])
|
||||
|
||||
alerter = AsyncMock()
|
||||
alerter.alert_governance = AsyncMock()
|
||||
agent = _make_agent(alerter=alerter)
|
||||
|
||||
with patch("src.services.governance_agent.get_db_context") as mock_ctx:
|
||||
with (
|
||||
patch("src.services.governance_agent.get_db_context") as mock_ctx,
|
||||
patch(
|
||||
"src.services.governance_agent.create_dispatch",
|
||||
new=AsyncMock(),
|
||||
) as mock_create_dispatch,
|
||||
):
|
||||
mock_ctx.return_value.__aenter__ = AsyncMock(return_value=mock_db)
|
||||
mock_ctx.return_value.__aexit__ = AsyncMock(return_value=False)
|
||||
|
||||
@@ -256,9 +263,49 @@ class TestCheckKnowledgeDegradation:
|
||||
assert "OpenClaw" in payload["ownership"]["support_agents"][0]
|
||||
assert "ElephantAlpha" in payload["ownership"]["support_agents"][1]
|
||||
assert payload["ownership"]["human_owner"] == "KM owner / SRE owner"
|
||||
mock_create_dispatch.assert_awaited_once()
|
||||
dispatch_kwargs = mock_create_dispatch.call_args.kwargs
|
||||
assert dispatch_kwargs["event_type"] == "knowledge_degradation"
|
||||
assert dispatch_kwargs["executor_type"] == "hermes_kb_growth_healthcheck"
|
||||
assert dispatch_kwargs["created_by"] == "governance_agent_intake"
|
||||
assert dispatch_kwargs["decision_context"]["next_action"] == "run_kb_growth_healthcheck"
|
||||
assert dispatch_kwargs["decision_context"]["ownership"]["lead_agent"] == "Hermes"
|
||||
assert dispatch_kwargs["decision_context"]["workflow"]["writes_km_without_approval"] is False
|
||||
assert result["stale"] == 3
|
||||
assert result["ratio"] == 0.3
|
||||
|
||||
def test_knowledge_degradation_dispatch_context(self):
|
||||
"""intake dispatch context 必須能被 Work Items 直接讀出 owner / stage / next_action."""
|
||||
from src.services.governance_agent import _build_knowledge_degradation_dispatch_context
|
||||
|
||||
ctx = _build_knowledge_degradation_dispatch_context(
|
||||
"evt-km-001",
|
||||
{
|
||||
"impact": {
|
||||
"stale_count": 1451,
|
||||
"total_count": 1870,
|
||||
"stale_ratio": 0.776,
|
||||
"threshold": 0.2,
|
||||
"stale_days": 7,
|
||||
},
|
||||
"remediation": {"next_action": "run_kb_growth_healthcheck"},
|
||||
"ownership": {
|
||||
"lead_agent": "Hermes",
|
||||
"support_agents": ["OpenClaw", "ElephantAlpha"],
|
||||
"human_owner": "KM owner / SRE owner",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert ctx["trigger_source"] == "governance_agent"
|
||||
assert ctx["next_action"] == "run_kb_growth_healthcheck"
|
||||
assert ctx["decision_path"] == "pending_owner_review"
|
||||
assert ctx["ownership"]["lead_agent"] == "Hermes"
|
||||
assert ctx["workflow"]["current_stage"] == "queued_kb_healthcheck"
|
||||
assert ctx["workflow"]["stage_by_dispatch_status"]["executing"] == "draft_km_updates"
|
||||
assert ctx["workflow"]["writes_km_without_approval"] is False
|
||||
assert ctx["extra"]["event_id"] == "evt-km-001"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# check_llm_hallucination
|
||||
|
||||
@@ -216,6 +216,43 @@ class TestDispatchGovernanceEvent:
|
||||
assert result is None
|
||||
mock_create.assert_not_awaited()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_knowledge_degradation_creates_kb_healthcheck_intake(self):
|
||||
"""knowledge_degradation 應先建立 non-executing Hermes KB healthcheck work item。"""
|
||||
event = _make_governance_event(event_type="knowledge_degradation")
|
||||
event.details = {
|
||||
"impact": {"stale_count": 1451, "total_count": 1870, "stale_ratio": 0.776},
|
||||
"remediation": {"next_action": "run_kb_growth_healthcheck"},
|
||||
"ownership": {"lead_agent": "Hermes"},
|
||||
}
|
||||
mock_dispatch_row = MagicMock()
|
||||
mock_dispatch_row.id = "dispatch-kb-001"
|
||||
|
||||
with (
|
||||
patch(
|
||||
"src.services.governance_dispatcher.get_active_for_event",
|
||||
new=AsyncMock(return_value=None),
|
||||
),
|
||||
patch(
|
||||
"src.services.governance_dispatcher.get_decision_fusion_adapter",
|
||||
) as mock_adapter_factory,
|
||||
patch(
|
||||
"src.services.governance_dispatcher.create_dispatch",
|
||||
new=AsyncMock(return_value=mock_dispatch_row),
|
||||
) as mock_create,
|
||||
):
|
||||
from src.services.governance_dispatcher import dispatch_governance_event
|
||||
result = await dispatch_governance_event(event)
|
||||
|
||||
assert result == "dispatch-kb-001"
|
||||
mock_adapter_factory.assert_not_called()
|
||||
mock_create.assert_awaited_once()
|
||||
dispatch_kwargs = mock_create.call_args.kwargs
|
||||
assert dispatch_kwargs["executor_type"] == "hermes_kb_growth_healthcheck"
|
||||
assert dispatch_kwargs["created_by"] == "governance_dispatcher_intake"
|
||||
assert dispatch_kwargs["decision_context"]["next_action"] == "run_kb_growth_healthcheck"
|
||||
assert dispatch_kwargs["decision_context"]["workflow"]["current_stage"] == "queued_kb_healthcheck"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_llm_failure_fallback_to_skip(self):
|
||||
"""fusion adapter 拋 Exception → fallback skip,不寫 dispatch,返回 None。"""
|
||||
|
||||
Reference in New Issue
Block a user