fix(sre): bind Agent99 closure to typed verifier
This commit is contained in:
@@ -17,6 +17,9 @@ from src.api.v1 import agents as agents_api
|
||||
from src.core.config import settings
|
||||
from src.models.agent99_completion import Agent99CompletionCallbackRequest
|
||||
from src.services import agent99_completion_callback as callback_service
|
||||
from src.services.agent99_controlled_dispatch_ledger import (
|
||||
build_agent99_dispatch_identity,
|
||||
)
|
||||
|
||||
|
||||
def payload() -> dict:
|
||||
@@ -132,6 +135,27 @@ def test_completion_callback_requires_durable_readback(monkeypatch) -> None:
|
||||
assert response.json()["detail"] == "agent99_completion_durable_readback_failed"
|
||||
|
||||
|
||||
def test_completion_callback_keeps_unverified_closure_pending(monkeypatch) -> None:
|
||||
monkeypatch.setattr(settings, "AGENT99_SRE_ALERT_RELAY_TOKEN", "expected")
|
||||
|
||||
async def fake_record(_request):
|
||||
return {
|
||||
"ok": False,
|
||||
"durable_readback": True,
|
||||
"status": "closure_authority_pending",
|
||||
}
|
||||
|
||||
monkeypatch.setattr(agents_api, "record_agent99_completion_callback", fake_record)
|
||||
response = app_client().post(
|
||||
"/api/v1/agents/agent99/completion-callback",
|
||||
headers={"X-Agent99-Completion-Token": "expected"},
|
||||
json=payload(),
|
||||
)
|
||||
|
||||
assert response.status_code == 503
|
||||
assert response.json()["detail"] == "closure_authority_pending"
|
||||
|
||||
|
||||
def test_completion_callback_returns_same_trace_receipt(monkeypatch) -> None:
|
||||
monkeypatch.setattr(settings, "AGENT99_SRE_ALERT_RELAY_TOKEN", "expected")
|
||||
|
||||
@@ -168,8 +192,301 @@ class FakeOperationRepository:
|
||||
return SimpleNamespace(id=f"operation-{len(self.events)}")
|
||||
|
||||
|
||||
def authorize_terminal_callback(monkeypatch) -> None:
|
||||
async def fake_authority(_payload):
|
||||
return {
|
||||
"required": True,
|
||||
"verified": True,
|
||||
"blocker": "none",
|
||||
"typed_domain": "control_plane_recovery",
|
||||
"route_id": "agent99:host_recovery:Recover",
|
||||
}
|
||||
|
||||
monkeypatch.setattr(
|
||||
callback_service,
|
||||
"_load_callback_closure_authority",
|
||||
fake_authority,
|
||||
)
|
||||
|
||||
|
||||
def typed_dispatch_callback_contract() -> tuple[
|
||||
Agent99CompletionCallbackRequest,
|
||||
dict,
|
||||
]:
|
||||
identity = build_agent99_dispatch_identity(
|
||||
project_id="awoooi",
|
||||
incident_id="INC-20260711-001",
|
||||
source_fingerprint="agent99-callback-source",
|
||||
route_id="agent99_cold_start_recovery",
|
||||
work_item_id="agent99-incident:INC-20260711-001",
|
||||
)
|
||||
verifier_name = "recover_post_condition_v1"
|
||||
request = Agent99CompletionCallbackRequest.model_validate({
|
||||
**payload(),
|
||||
"run_id": str(identity.run_id),
|
||||
"trace_id": identity.trace_id,
|
||||
"work_item_id": identity.work_item_id,
|
||||
"verifier_name": verifier_name,
|
||||
})
|
||||
receipt = {
|
||||
"schema_version": "agent99_controlled_dispatch_receipt_v1",
|
||||
"status": "verifier_passed_learning_writeback_pending",
|
||||
"identity": identity.public_dict(),
|
||||
"run_id": str(identity.run_id),
|
||||
"trace_id": identity.trace_id,
|
||||
"run_state": "waiting_tool",
|
||||
"dispatch_promoted": True,
|
||||
"controlled_apply_authorized": True,
|
||||
"post_verifier_passed": True,
|
||||
"dispatch_scope": {
|
||||
"schema_version": "agent99_dispatch_scope_v1",
|
||||
"kind": "host_recovery",
|
||||
"suggested_mode": "Recover",
|
||||
"target_resource": "cold-start-gate",
|
||||
"controlled_apply_requested": True,
|
||||
"canonical_asset_id": "windows-vmware:host_99",
|
||||
"typed_domain": "control_plane_recovery",
|
||||
"executor": "Agent99",
|
||||
"verifier": verifier_name,
|
||||
"route_id": identity.route_id,
|
||||
},
|
||||
"verifier": {
|
||||
"schema_version": "agent99_independent_verifier_receipt_v1",
|
||||
"status": "success",
|
||||
"run_id": str(identity.run_id),
|
||||
"trace_id": identity.trace_id,
|
||||
"work_item_id": identity.work_item_id,
|
||||
"outcome_state": "resolved",
|
||||
"transport_ok": True,
|
||||
"verifier_passed": True,
|
||||
"source_event_resolved": True,
|
||||
},
|
||||
}
|
||||
return request, receipt
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_callback_closure_authority_requires_exact_typed_ledger(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
request, dispatch_receipt = typed_dispatch_callback_contract()
|
||||
|
||||
async def fake_read(**_kwargs):
|
||||
return dispatch_receipt
|
||||
|
||||
monkeypatch.setattr(
|
||||
callback_service,
|
||||
"read_agent99_dispatch_receipt_for_run",
|
||||
fake_read,
|
||||
)
|
||||
|
||||
authority = await callback_service._load_callback_closure_authority(request)
|
||||
|
||||
assert authority == {
|
||||
"required": True,
|
||||
"verified": True,
|
||||
"blocker": "none",
|
||||
"typed_domain": "control_plane_recovery",
|
||||
"route_id": "agent99_cold_start_recovery",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_callback_closure_authority_rejects_cross_domain_scope(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
request, dispatch_receipt = typed_dispatch_callback_contract()
|
||||
dispatch_receipt["dispatch_scope"]["typed_domain"] = "docker_compose"
|
||||
|
||||
async def fake_read(**_kwargs):
|
||||
return dispatch_receipt
|
||||
|
||||
monkeypatch.setattr(
|
||||
callback_service,
|
||||
"read_agent99_dispatch_receipt_for_run",
|
||||
fake_read,
|
||||
)
|
||||
|
||||
authority = await callback_service._load_callback_closure_authority(request)
|
||||
|
||||
assert authority["verified"] is False
|
||||
assert authority["blocker"] == "typed_dispatch_scope_mismatch"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"field",
|
||||
["transport_ok", "verifier_passed", "source_event_resolved"],
|
||||
)
|
||||
async def test_callback_closure_authority_rejects_false_verifier_projection(
|
||||
monkeypatch,
|
||||
field: str,
|
||||
) -> None:
|
||||
request, dispatch_receipt = typed_dispatch_callback_contract()
|
||||
request = request.model_copy(update={field: False})
|
||||
|
||||
async def fake_read(**_kwargs):
|
||||
return dispatch_receipt
|
||||
|
||||
monkeypatch.setattr(
|
||||
callback_service,
|
||||
"read_agent99_dispatch_receipt_for_run",
|
||||
fake_read,
|
||||
)
|
||||
|
||||
authority = await callback_service._load_callback_closure_authority(request)
|
||||
|
||||
assert authority["verified"] is False
|
||||
assert authority["blocker"] == "callback_verifier_projection_mismatch"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_completion_service_does_not_resolve_from_caller_booleans(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
request = Agent99CompletionCallbackRequest.model_validate(payload())
|
||||
event_id = UUID("3c9e8ee7-fb21-5420-afae-ef928f75a58c")
|
||||
repository = FakeOperationRepository()
|
||||
readbacks = iter([
|
||||
None,
|
||||
{
|
||||
"event_id": str(event_id),
|
||||
"run_id": "5e5b0080-e8c9-53a7-872b-0fc1ce80f11f",
|
||||
"operation_count": 1,
|
||||
"execution_count": 1,
|
||||
"resolved_count": 0,
|
||||
},
|
||||
])
|
||||
|
||||
async def fake_authority(_payload):
|
||||
return {
|
||||
"required": True,
|
||||
"verified": False,
|
||||
"blocker": "dispatch_run_not_found",
|
||||
"typed_domain": "",
|
||||
"route_id": "",
|
||||
}
|
||||
|
||||
async def fake_readback(**_kwargs):
|
||||
return next(readbacks)
|
||||
|
||||
async def fake_record_event(**kwargs):
|
||||
assert kwargs["stage"] == "verifying"
|
||||
assert kwargs["severity"] == "warning"
|
||||
return event_id
|
||||
|
||||
monkeypatch.setattr(
|
||||
callback_service,
|
||||
"_load_callback_closure_authority",
|
||||
fake_authority,
|
||||
)
|
||||
monkeypatch.setattr(callback_service, "_load_callback_readback", fake_readback)
|
||||
monkeypatch.setattr(callback_service, "record_external_alert_event", fake_record_event)
|
||||
monkeypatch.setattr(
|
||||
callback_service,
|
||||
"get_alert_operation_log_repository",
|
||||
lambda: repository,
|
||||
)
|
||||
|
||||
receipt = await callback_service.record_agent99_completion_callback(request)
|
||||
|
||||
assert receipt["ok"] is False
|
||||
assert receipt["durable_readback"] is True
|
||||
assert receipt["outcome_state"] == "verifying"
|
||||
assert receipt["closure_allowed"] is False
|
||||
assert receipt["closure_blocker"] == "dispatch_run_not_found"
|
||||
assert [item[0] for item in repository.events] == ["EXECUTION_COMPLETED"]
|
||||
assert repository.events[0][1]["success"] is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_completion_service_retry_adds_only_resolved_receipt(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
request = Agent99CompletionCallbackRequest.model_validate(payload())
|
||||
verifying_event_id = UUID("3c9e8ee7-fb21-5420-afae-ef928f75a58c")
|
||||
resolved_event_id = UUID("5e5b0080-e8c9-53a7-872b-0fc1ce80f11f")
|
||||
awooop_run_id = "8b877ed3-037f-5463-954b-f4b77a77d786"
|
||||
repository = FakeOperationRepository()
|
||||
authority_ready = {"value": False}
|
||||
readbacks = iter([
|
||||
None,
|
||||
{
|
||||
"event_id": str(verifying_event_id),
|
||||
"run_id": awooop_run_id,
|
||||
"operation_count": 1,
|
||||
"execution_count": 1,
|
||||
"resolved_count": 0,
|
||||
},
|
||||
{
|
||||
"event_id": None,
|
||||
"run_id": None,
|
||||
"operation_count": 1,
|
||||
"execution_count": 1,
|
||||
"resolved_count": 0,
|
||||
},
|
||||
{
|
||||
"event_id": str(resolved_event_id),
|
||||
"run_id": awooop_run_id,
|
||||
"operation_count": 2,
|
||||
"execution_count": 1,
|
||||
"resolved_count": 1,
|
||||
},
|
||||
])
|
||||
event_calls: list[dict] = []
|
||||
|
||||
async def fake_authority(_payload):
|
||||
verified = authority_ready["value"]
|
||||
return {
|
||||
"required": True,
|
||||
"verified": verified,
|
||||
"blocker": "none" if verified else "independent_verifier_missing",
|
||||
"typed_domain": "control_plane_recovery" if verified else "",
|
||||
"route_id": "agent99_cold_start_recovery" if verified else "",
|
||||
}
|
||||
|
||||
async def fake_readback(**_kwargs):
|
||||
return next(readbacks)
|
||||
|
||||
async def fake_record_event(**kwargs):
|
||||
event_calls.append(kwargs)
|
||||
return (
|
||||
resolved_event_id
|
||||
if kwargs["stage"] == "resolved"
|
||||
else verifying_event_id
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
callback_service,
|
||||
"_load_callback_closure_authority",
|
||||
fake_authority,
|
||||
)
|
||||
monkeypatch.setattr(callback_service, "_load_callback_readback", fake_readback)
|
||||
monkeypatch.setattr(callback_service, "record_external_alert_event", fake_record_event)
|
||||
monkeypatch.setattr(
|
||||
callback_service,
|
||||
"get_alert_operation_log_repository",
|
||||
lambda: repository,
|
||||
)
|
||||
|
||||
pending = await callback_service.record_agent99_completion_callback(request)
|
||||
authority_ready["value"] = True
|
||||
resolved = await callback_service.record_agent99_completion_callback(request)
|
||||
|
||||
assert pending["ok"] is False
|
||||
assert resolved["ok"] is True
|
||||
assert resolved["duplicate"] is True
|
||||
assert [item[0] for item in repository.events] == [
|
||||
"EXECUTION_COMPLETED",
|
||||
"RESOLVED",
|
||||
]
|
||||
assert [call["stage"] for call in event_calls] == ["verifying", "resolved"]
|
||||
assert [call["is_duplicate"] for call in event_calls] == [False, True]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_completion_service_writes_event_run_and_operation_receipts(monkeypatch) -> None:
|
||||
authorize_terminal_callback(monkeypatch)
|
||||
request = Agent99CompletionCallbackRequest.model_validate(payload())
|
||||
event_id = UUID("3c9e8ee7-fb21-5420-afae-ef928f75a58c")
|
||||
awooop_run_id = "5e5b0080-e8c9-53a7-872b-0fc1ce80f11f"
|
||||
@@ -221,6 +538,7 @@ async def test_completion_service_writes_event_run_and_operation_receipts(monkey
|
||||
async def test_completion_service_compacts_long_alert_id_for_operation_log(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
authorize_terminal_callback(monkeypatch)
|
||||
external_alert_id = "awoooi-agent99-ae36a454-c9eb-5f2d-9e35-be852b3eac9b"
|
||||
request = Agent99CompletionCallbackRequest.model_validate(
|
||||
{**payload(), "alert_id": external_alert_id}
|
||||
@@ -267,6 +585,7 @@ async def test_completion_service_compacts_long_alert_id_for_operation_log(
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_completion_service_is_idempotent(monkeypatch) -> None:
|
||||
authorize_terminal_callback(monkeypatch)
|
||||
request = Agent99CompletionCallbackRequest.model_validate(payload())
|
||||
event_id = UUID("3c9e8ee7-fb21-5420-afae-ef928f75a58c")
|
||||
existing = {
|
||||
@@ -304,6 +623,7 @@ async def test_completion_service_is_idempotent(monkeypatch) -> None:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_completion_service_repairs_partial_terminal_receipt(monkeypatch) -> None:
|
||||
authorize_terminal_callback(monkeypatch)
|
||||
request = Agent99CompletionCallbackRequest.model_validate(payload())
|
||||
event_id = UUID("3c9e8ee7-fb21-5420-afae-ef928f75a58c")
|
||||
before = {
|
||||
|
||||
Reference in New Issue
Block a user