feat(telegram): expose verified automation evidence
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user