feat(telegram): expose verified automation evidence

This commit is contained in:
Your Name
2026-07-19 00:54:49 +08:00
parent ca5853606d
commit dfe75456ab
4 changed files with 287 additions and 19 deletions

View File

@@ -888,9 +888,25 @@ def test_terminal_candidate_contract_requires_verified_four_node_chain() -> None
async def test_projection_replay_sends_missing_receipt_without_reapplying(
monkeypatch: pytest.MonkeyPatch,
) -> None:
closure_coverage = {
"telegram_receipt": False,
"candidate": True,
"check_mode": True,
"controlled_apply": True,
"post_apply_verifier": True,
"auto_repair_execution_receipt": True,
"km_playbook_writeback": True,
"rag_writeback": True,
"mcp_context": True,
"playbook_trust": True,
"timeline_projection": True,
"approval_projection": True,
"execution_lifecycle": True,
"required_runtime_stage_receipts": True,
}
readback = AsyncMock(
side_effect=[
{"receipts": {"telegram_receipt": False}},
{"receipts": closure_coverage},
{"receipts": {"telegram_receipt": True}},
]
)
@@ -937,6 +953,68 @@ async def test_projection_replay_sends_missing_receipt_without_reapplying(
assert result["closed"] is True
assert result["runtime_apply_executed"] is False
telegram_sender.assert_awaited_once()
assert telegram_sender.await_args.kwargs["closure_receipts"] == (
closure_coverage
)
@pytest.mark.asyncio
async def test_alert_chain_final_receipt_projects_all_learning_evidence(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway = AsyncMock()
gateway.send_controlled_apply_result_receipt.return_value = {
"ok": True,
"_awooop_outbound_mirror_acknowledged": True,
}
monkeypatch.setattr(
"src.services.telegram_gateway.get_telegram_gateway",
lambda: gateway,
)
base = _claim()
claim = replace(
base,
catalog_id="ansible:110-alertmanager-delivery-recovery",
playbook_path=(
"infra/ansible/playbooks/110-alertmanager-delivery-recovery.yml"
),
apply_playbook_path=(
"infra/ansible/playbooks/110-alertmanager-delivery-recovery.yml"
),
input_payload={
**base.input_payload,
"alert_category": "alert_chain_health",
},
)
acknowledged = await service._send_controlled_apply_telegram_receipt(
claim,
_verified_result(),
apply_op_id="00000000-0000-0000-0000-000000000104",
writeback={
"verification_result": "success",
"verification": True,
"learning": True,
"rag_writeback": True,
"trust_learning": True,
},
closure_receipts={
"km_playbook_writeback": True,
"rag_writeback": True,
"mcp_context": True,
"playbook_trust": True,
},
project_id="awoooi",
)
assert acknowledged is True
call = gateway.send_controlled_apply_result_receipt.await_args.kwargs
assert call["alert_category"] == "alert_chain_health"
assert call["verifier_written"] is True
assert call["learning_written"] is True
assert call["rag_written"] is True
assert call["mcp_context_ready"] is True
assert call["playbook_trust_written"] is True
@pytest.mark.asyncio
@@ -960,10 +1038,11 @@ async def test_projection_stays_open_until_durable_telegram_readback(
]
),
)
telegram_sender = AsyncMock(return_value=False)
monkeypatch.setattr(
service,
"_send_controlled_apply_telegram_receipt",
AsyncMock(return_value=False),
telegram_sender,
)
monkeypatch.setattr(
service,
@@ -994,6 +1073,7 @@ async def test_projection_stays_open_until_durable_telegram_readback(
assert result["telegram_receipt_acknowledged"] is False
assert result["telegram_lifecycle_written"] is False
assert lifecycle.await_count == 1
telegram_sender.assert_not_awaited()
@pytest.mark.asyncio

View File

@@ -1309,6 +1309,10 @@ async def test_controlled_apply_result_receipt_marks_callback_reply_evidence(mon
duration_ms=7727,
verifier_written=True,
learning_written=True,
rag_written=True,
mcp_context_ready=True,
playbook_trust_written=True,
alert_category="backup_restore",
project_id="awoooi",
)
@@ -1320,6 +1324,16 @@ async def test_controlled_apply_result_receipt_marks_callback_reply_evidence(mon
assert "Run:" in payload["text"]
assert "INC-20260627-64472B" in payload["text"]
assert "ansible:188-momo-backup-user" in payload["text"]
assert "分類: <code>BACKUP_RESTORE</code>" in payload["text"]
assert "Agent: <code>Host Ansible executor</code>" in payload["text"]
assert "LLM: <code>no runtime apply authority</code>" in payload["text"]
assert (
"證據: verifier <code>yes</code> | KM <code>yes</code> "
"| RAG <code>yes</code> | MCP <code>yes</code> "
"| PlayBook <code>yes</code>"
) in payload["text"]
assert payload["text"].count("PlayBook:") == 1
assert "後續: <code>monitor_for_regression</code>" in payload["text"]
source_extra = payload["_awooop_source_envelope_extra"]
assert source_extra["callback_reply"]["action"] == "controlled_apply_result"
assert source_extra["callback_reply"]["status"] == "callback_reply_sent"
@@ -1331,6 +1345,19 @@ async def test_controlled_apply_result_receipt_marks_callback_reply_evidence(mon
"00000000-0000-0000-0000-000000000001"
]
assert source_extra["outbound_message_type"] == "final"
evidence = source_extra["callback_reply"]["automation_evidence"]
assert evidence == {
"schema_version": "controlled_apply_automation_evidence_v1",
"independent_verifier": True,
"km_writeback": True,
"rag_writeback": True,
"mcp_context": True,
"playbook_trust": True,
"complete": True,
"executor_agent": "Host Ansible executor",
"llm_runtime_apply_authority": False,
}
assert source_extra["callback_reply"]["alert_category"] == "backup_restore"
assert (
telegram_gateway_module._infer_outbound_message_type(
payload["text"],
@@ -1352,6 +1379,40 @@ async def test_controlled_apply_result_receipt_marks_callback_reply_evidence(mon
assert snapshot["operator_outcome"]["state"] == "completed_verified"
assert snapshot["execution"]["ansible"]["controlled_apply"] is True
sent_requests.clear()
await gateway.send_controlled_apply_result_receipt(
automation_run_id="00000000-0000-0000-0000-000000000011",
incident_id="INC-20260627-64472B",
catalog_id="ansible:110-alertmanager-delivery-recovery",
apply_op_id="73b7a95c-3652-4c0d-bb4c-729e500acedc",
playbook_path=(
"infra/ansible/playbooks/110-alertmanager-delivery-recovery.yml"
),
verification_result="success",
returncode=0,
duration_ms=880,
verifier_written=True,
learning_written=True,
rag_written=False,
mcp_context_ready=True,
playbook_trust_written=True,
project_id="awoooi",
)
partial = sent_requests[0][1]
assert "執行完成、證據待補" in partial["text"]
assert "分類: <code>ALERT_CHAIN_HEALTH</code>" in partial["text"]
assert "RAG <code>no</code>" in partial["text"]
assert "complete_missing_evidence_receipts" in partial["text"]
assert (
partial["_awooop_source_envelope_extra"]["outbound_message_type"]
== "interim"
)
assert (
partial["_awooop_source_envelope_extra"]["callback_reply"]
["automation_evidence"]["complete"]
is False
)
@pytest.mark.asyncio
async def test_no_write_replay_receipt_never_claims_runtime_apply(monkeypatch) -> None: