fix(agent99): terminalize unscoped cold-start recovery
This commit is contained in:
@@ -815,6 +815,75 @@ def test_agent99_dispatch_identity_is_stable_across_projections() -> None:
|
||||
assert telegram.work_item_id == alertmanager.work_item_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_named_cold_start_without_inventory_scope_stops_before_claim(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
class NoClaimLedger:
|
||||
async def reserve(self, **_kwargs): # type: ignore[no-untyped-def]
|
||||
raise AssertionError("no-write terminal must not reserve a run")
|
||||
|
||||
async def unexpected_single_flight(*_args, **_kwargs): # type: ignore[no-untyped-def]
|
||||
raise AssertionError("no-write terminal must not claim single-flight")
|
||||
|
||||
monkeypatch.setattr(
|
||||
"src.services.agent99_sre_bridge.get_agent99_dispatch_ledger",
|
||||
lambda: NoClaimLedger(),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"src.services.agent99_sre_bridge.acquire_agent99_sre_single_flight",
|
||||
unexpected_single_flight,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"src.services.agent99_sre_bridge.dispatch_agent99_sre_alert_with_receipt",
|
||||
lambda _payload: (_ for _ in ()).throw(
|
||||
AssertionError("no-write terminal must not perform transport")
|
||||
),
|
||||
)
|
||||
|
||||
result = await bridge_alertmanager_to_agent99(
|
||||
alert_id="alert-11c751",
|
||||
alertname="ColdStartGateBlocked",
|
||||
severity="critical",
|
||||
namespace="default",
|
||||
target_resource="cold-start-gate",
|
||||
message="cold-start gate blocked",
|
||||
labels={"host": "110"},
|
||||
fingerprint="source-fingerprint",
|
||||
project_id="awoooi",
|
||||
incident_id="INC-20260711-11C751",
|
||||
approval_id="00000000-0000-0000-0000-000000000751",
|
||||
route_id="agent99:host_recovery:Recover",
|
||||
work_item_id="agent99-dispatch:awoooi:INC-20260711-11C751:cold-start",
|
||||
)
|
||||
|
||||
assert result["status"] == "no_write_terminal"
|
||||
assert result["dispatchPerformed"] is False
|
||||
terminal = result["noWriteTerminal"]
|
||||
assert terminal["source_alert_id"] == "alert-11c751"
|
||||
assert terminal["work_item_id"] == (
|
||||
"agent99-dispatch:awoooi:INC-20260711-11C751:cold-start"
|
||||
)
|
||||
assert terminal["source_namespace"] == "default"
|
||||
assert terminal["normalized_execution_namespace"] == "host_recovery"
|
||||
assert terminal["kubernetes_namespace_applicable"] is False
|
||||
assert terminal["playbook"] == {
|
||||
"check": "Agent99 Status controlledApply=false",
|
||||
"apply": "blocked_exact_inventory_scope_missing",
|
||||
"rollback": "not_applicable_no_runtime_write",
|
||||
}
|
||||
assert terminal["transport"] == {
|
||||
"status": "suppressed_before_single_flight_claim",
|
||||
"claim_performed": False,
|
||||
"transport_performed": False,
|
||||
}
|
||||
assert terminal["verifier_contract"]["status"] == (
|
||||
"ready_for_independent_source_validation"
|
||||
)
|
||||
assert terminal["verifier_contract"]["runtime_verifier_applicable"] is False
|
||||
assert terminal["runtime_write_performed"] is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mutating_dispatch_is_deduplicated_by_postgres_identity(
|
||||
monkeypatch,
|
||||
|
||||
@@ -56,6 +56,10 @@ def test_normalizer_separates_source_namespace_from_execution_domain() -> None:
|
||||
assert route["normalized_execution_namespace"] == "host_recovery"
|
||||
assert route["kubernetes_namespace_applicable"] is False
|
||||
assert route["executor"] == "Agent99"
|
||||
assert route["canonical_asset_id"] == "control-plane:cold-start-gate"
|
||||
assert route["allowed_inventory_hosts"] == []
|
||||
assert route["controlled_apply_allowed"] is False
|
||||
assert route["no_write_terminal_required"] is True
|
||||
assert route["runtime_execution_authorized"] is False
|
||||
|
||||
assert resolve_controlled_alert_target(
|
||||
@@ -76,11 +80,12 @@ def test_cold_start_contract_requires_same_run_receipts_before_closure() -> None
|
||||
assert contract is not None
|
||||
fields = {row["field"]: row for row in contract["fields"]}
|
||||
assert fields["target_selector"]["status"] == "ready"
|
||||
assert fields["controlled_apply_route"]["status"] == "ready"
|
||||
assert fields["controlled_apply_route"]["status"] == "blocked"
|
||||
for required in (
|
||||
"source_sensor_receipt",
|
||||
"check_mode_receipt",
|
||||
"dispatch_receipt",
|
||||
"no_write_terminal_receipt",
|
||||
"post_apply_verifier",
|
||||
"incident_closure_receipt",
|
||||
"telegram_receipt",
|
||||
@@ -91,7 +96,11 @@ def test_cold_start_contract_requires_same_run_receipts_before_closure() -> None
|
||||
assert contract["runtime_execution_authorized"] is False
|
||||
assert contract["owner_review_required"] is False
|
||||
assert contract["needs_human"] is False
|
||||
assert contract["completion_status"] == "partial"
|
||||
assert contract["controlled_playbook_queue"] is False
|
||||
assert contract["completion_status"] == (
|
||||
"no_write_terminal_pending_durable_receipt"
|
||||
)
|
||||
assert contract["completion_blocker"] == "exact_inventory_scope_missing"
|
||||
|
||||
|
||||
def test_cold_start_is_excluded_from_generic_ansible_keyword_catalog() -> None:
|
||||
@@ -139,7 +148,7 @@ def test_cold_start_does_not_emit_ansible_candidate_audit() -> None:
|
||||
assert payload is None
|
||||
|
||||
|
||||
def test_handoff_stays_partial_until_dispatch_verifier_and_learning() -> None:
|
||||
def test_handoff_requires_no_write_terminal_until_inventory_scope_is_exact() -> None:
|
||||
handoff = build_controlled_recovery_handoff(
|
||||
alertname="custom",
|
||||
target_resource="cold-start-gate",
|
||||
@@ -154,44 +163,66 @@ def test_handoff_stays_partial_until_dispatch_verifier_and_learning() -> None:
|
||||
assert handoff["runtime_execution_authorized"] is False
|
||||
assert handoff["owner_review_required"] is False
|
||||
assert handoff["needs_human"] is False
|
||||
assert handoff["active_blockers"] == [
|
||||
"agent99_dispatch_receipt_readback_pending",
|
||||
"post_apply_verifier_missing",
|
||||
"km_playbook_writeback_missing",
|
||||
]
|
||||
assert handoff["status"] == "cold_start_no_write_terminal_pending_receipt"
|
||||
assert handoff["active_blockers"] == ["exact_inventory_scope_missing"]
|
||||
assert handoff["safe_next_action"] == (
|
||||
"persist_no_write_terminal_without_claim_or_transport"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_webhook_router_never_queues_generic_ansible_for_cold_start(
|
||||
@pytest.mark.parametrize(
|
||||
"operation_receipt_id",
|
||||
["no-write-receipt-11c751", "", None],
|
||||
)
|
||||
async def test_webhook_router_persists_named_cold_start_no_write_terminal(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
operation_receipt_id: str | None,
|
||||
) -> None:
|
||||
queue = AsyncMock(side_effect=AssertionError("generic Ansible must not be queued"))
|
||||
append = AsyncMock()
|
||||
append = AsyncMock(
|
||||
return_value=(
|
||||
SimpleNamespace(id=operation_receipt_id)
|
||||
if operation_receipt_id is not None
|
||||
else None
|
||||
)
|
||||
)
|
||||
incident_lookup = AsyncMock(side_effect=AssertionError("incident lookup not needed"))
|
||||
bridge = AsyncMock(
|
||||
return_value={
|
||||
"status": "dispatched",
|
||||
"dispatchPerformed": True,
|
||||
"identity": {
|
||||
"run_id": "7cf8fdf7-0966-5ac6-95b2-09e32808b248",
|
||||
"trace_id": "00-11111111111111111111111111111111-2222222222222222-01",
|
||||
"work_item_id": "agent99-dispatch:awoooi:INC-20260711-11C751:recovery",
|
||||
"idempotency_key": "agent99-controlled:stable-key",
|
||||
},
|
||||
"dispatchReceipt": {
|
||||
"status": "accepted_queue_persisted",
|
||||
"accepted": True,
|
||||
"inbox_triggered": True,
|
||||
"queue_accepted": True,
|
||||
"dispatch_identity_matched": True,
|
||||
},
|
||||
"correlatedReceipt": {
|
||||
"status": "dispatch_accepted_verifier_pending",
|
||||
"receipt_persisted": True,
|
||||
"runtime_execution_authorized": False,
|
||||
"runtime_closure_verified": False,
|
||||
"verifier": {"status": "pending"},
|
||||
"learning_writeback": {"status": "pending_verifier"},
|
||||
"status": "no_write_terminal",
|
||||
"dispatchPerformed": False,
|
||||
"runtimeWritePerformed": False,
|
||||
"controlledApplyAuthorized": False,
|
||||
"runtimeClosureVerified": False,
|
||||
"noWriteTerminal": {
|
||||
"schema_version": "agent99_cold_start_no_write_terminal_v1",
|
||||
"status": "no_write_terminal",
|
||||
"reason": "exact_inventory_scope_missing",
|
||||
"incident_id": "INC-20260711-11C751",
|
||||
"route_id": "agent99:host_recovery:Recover",
|
||||
"canonical_asset_id": "control-plane:cold-start-gate",
|
||||
"source_namespace": "default",
|
||||
"normalized_execution_namespace": "host_recovery",
|
||||
"kubernetes_namespace_applicable": False,
|
||||
"allowed_inventory_hosts": [],
|
||||
"runtime_write_performed": False,
|
||||
"agent99_dispatch_performed": False,
|
||||
"production_executor_invoked": False,
|
||||
"incident_resolution_allowed": False,
|
||||
"transport": {
|
||||
"claim_performed": False,
|
||||
"transport_performed": False,
|
||||
},
|
||||
"next_safe_action": (
|
||||
"bind_exact_inventory_scope_before_creating_a_new_recover_candidate"
|
||||
),
|
||||
"verifier_contract": {
|
||||
"name": "cold_start_no_write_terminal_contract",
|
||||
"status": "ready_for_independent_source_validation",
|
||||
"scope": "source_no_write_only",
|
||||
"runtime_verifier_applicable": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -235,19 +266,42 @@ async def test_webhook_router_never_queues_generic_ansible_for_cold_start(
|
||||
"agent99:host_recovery:Recover"
|
||||
)
|
||||
assert handoff["execution_priority"] == 30
|
||||
assert handoff["queued"] is True
|
||||
assert handoff["queued"] is False
|
||||
assert handoff["side_effect_performed"] is False
|
||||
if operation_receipt_id:
|
||||
assert handoff["status"] == "cold_start_no_write_terminal"
|
||||
assert handoff["receipt_persisted"] is True
|
||||
assert handoff["no_write_terminal_receipt_id"] == (
|
||||
"no-write-receipt-11c751"
|
||||
)
|
||||
else:
|
||||
assert handoff["status"] == (
|
||||
"cold_start_no_write_terminal_receipt_unavailable"
|
||||
)
|
||||
assert handoff["receipt_persisted"] is False
|
||||
assert handoff["active_blockers"] == [
|
||||
"durable_no_write_receipt_unavailable"
|
||||
]
|
||||
assert handoff["runtime_execution_authorized"] is False
|
||||
assert handoff["runtime_closure_verified"] is False
|
||||
assert handoff["automation_run_id"] == (
|
||||
"7cf8fdf7-0966-5ac6-95b2-09e32808b248"
|
||||
)
|
||||
append.assert_awaited_once()
|
||||
receipt = append.await_args.kwargs
|
||||
assert receipt["action_detail"] == "controlled_check_mode_queued"
|
||||
assert receipt["success"] is True
|
||||
assert receipt["action_detail"] == "controlled_no_write_terminal"
|
||||
assert receipt["success"] is None
|
||||
assert receipt["context"]["safe_next_action"] == (
|
||||
"run_independent_cold_start_verifier_then_km_playbook_writeback"
|
||||
"bind_exact_inventory_scope_before_creating_a_new_recover_candidate"
|
||||
)
|
||||
assert receipt["context"]["no_write_terminal"]["reason"] == (
|
||||
"exact_inventory_scope_missing"
|
||||
)
|
||||
assert receipt["context"]["no_write_verifier_receipt"] == {
|
||||
"schema_version": "cold_start_no_write_source_verifier_receipt_v1",
|
||||
"verifier": "alert_webhook_no_write_terminal_validator",
|
||||
"status": "passed_source_contract",
|
||||
"producer_independent": True,
|
||||
"runtime_verifier": False,
|
||||
"runtime_closure_verified": False,
|
||||
}
|
||||
assert receipt["context"]["runtime_closure_verified"] is False
|
||||
|
||||
|
||||
|
||||
@@ -155,8 +155,8 @@ def test_loader_returns_fixed_architecture_provider_order_and_agent99_bridge() -
|
||||
"active_or_completed_commitments": 68,
|
||||
"by_status": {
|
||||
"analysis_or_governance_complete": 6,
|
||||
"source_implemented_runtime_pending": 29,
|
||||
"in_progress": 31,
|
||||
"source_implemented_runtime_pending": 30,
|
||||
"in_progress": 30,
|
||||
"not_started_or_no_current_evidence": 2,
|
||||
"superseded": 2,
|
||||
},
|
||||
@@ -182,6 +182,12 @@ def test_loader_returns_fixed_architecture_provider_order_and_agent99_bridge() -
|
||||
assert "destination-bound provider acknowledgement" in " ".join(
|
||||
commitments["AIA-CONV-031"]["source_evidence"]
|
||||
)
|
||||
assert commitments["AIA-CONV-032"]["status"] == (
|
||||
"source_implemented_runtime_pending"
|
||||
)
|
||||
assert "blocks Recover before single-flight claim" in " ".join(
|
||||
commitments["AIA-CONV-032"]["source_evidence"]
|
||||
)
|
||||
assert "Host112" in commitments["AIA-CONV-049"]["title"]
|
||||
assert commitments["AIA-CONV-049"]["status"] == (
|
||||
"source_implemented_runtime_pending"
|
||||
|
||||
@@ -672,6 +672,11 @@ def test_agent99_payload_carries_same_typed_host_operations_contract() -> None:
|
||||
assert payload["awoooi"]["canonicalAssetId"] == (
|
||||
"control-plane:cold-start-gate"
|
||||
)
|
||||
assert route["source_namespace"] == "default"
|
||||
assert route["normalized_execution_namespace"] == "host_recovery"
|
||||
assert route["kubernetes_namespace_applicable"] is False
|
||||
assert route["controlled_apply_allowed"] is False
|
||||
assert route["no_write_terminal_required"] is True
|
||||
# Generic cold-start remains no-write until the router binds an exact
|
||||
# inventory scope; the Windows inbox rejects an empty scope fail-closed.
|
||||
assert route["host"] is None
|
||||
|
||||
Reference in New Issue
Block a user