fix(agent99): terminalize unscoped cold-start recovery

This commit is contained in:
Your Name
2026-07-22 14:17:58 +08:00
parent 3a6f8badb7
commit 9fce2cd00f
8 changed files with 466 additions and 64 deletions

View File

@@ -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,

View File

@@ -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

View File

@@ -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"

View File

@@ -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