diff --git a/apps/api/src/services/ai_agent_log_controlled_writeback_consumer_readback.py b/apps/api/src/services/ai_agent_log_controlled_writeback_consumer_readback.py index 5f3758950..94ffefde8 100644 --- a/apps/api/src/services/ai_agent_log_controlled_writeback_consumer_readback.py +++ b/apps/api/src/services/ai_agent_log_controlled_writeback_consumer_readback.py @@ -586,7 +586,9 @@ def _build_consumer_readback( "verifier_consumer_binding_count": _target_count(bindings, "verifier"), "ai_agent_consumer_binding_count": _target_count(bindings, "ai_agent"), "target_context_receipt_write_count": sum( - 1 for binding in bindings if binding["target_write_performed"] is True + 1 + for binding in bindings + if _binding_has_verified_target_context_write(binding) ), "km_context_receipt_write_count": _target_write_count(bindings, "km"), "rag_context_receipt_write_count": _target_write_count(bindings, "rag"), @@ -895,7 +897,7 @@ def _target_rollups(bindings: list[dict[str, Any]]) -> list[dict[str, Any]]: if binding["target"] == target ), "target_write_performed": any( - binding["target_write_performed"] is True + _binding_has_verified_target_context_write(binding) for binding in bindings if binding["target"] == target ), @@ -912,7 +914,23 @@ def _target_write_count(bindings: list[dict[str, Any]], target: str) -> int: return sum( 1 for binding in bindings - if binding["target"] == target and binding["target_write_performed"] is True + if binding["target"] == target + and _binding_has_verified_target_context_write(binding) + ) + + +def _binding_has_verified_target_context_write( + binding: Mapping[str, Any], +) -> bool: + receipt = _dict(binding.get("target_write_receipt")) + consumer_receipt_id = str(receipt.get("consumer_receipt_id") or "") + return bool( + binding.get("target_write_performed") is True + and consumer_receipt_id + and consumer_receipt_id == str(binding.get("consumer_receipt_id") or "") + and receipt.get("status") == "success" + and receipt.get("target_context_receipt_write_performed") is True + and receipt.get("runtime_target_write_performed") is True ) @@ -1050,6 +1068,7 @@ def _telegram_alert_context_receipts( status = str(binding.get("status") or "") ready = status == "ready_for_consumer_context" and bool(ref) receipts.append({ + "receipt_kind": "alert_card_learning_registry", "receipt_id": ( "telegram_alert_learning_context::" f"{message_id or 'unknown-message'}::{target}" @@ -1172,7 +1191,7 @@ def _telegram_alert_learning_context_with_consumer_fallback( binding for binding in bindings if binding.get("status") == "ready_for_consumer_context" - and binding.get("target_write_performed") is True + and _binding_has_verified_target_context_write(binding) and binding.get("raw_payload_included") is False and binding.get("target") in _TARGETS ] @@ -1237,6 +1256,7 @@ def _telegram_alert_context_receipt_from_consumer_binding( ) verifier_refs = _strings(binding.get("post_apply_verifier_refs")) return { + "receipt_kind": "log_controlled_writeback_consumer", "receipt_id": ( "telegram_alert_learning_context_fallback::" f"{source_receipt_id or target}" @@ -1256,7 +1276,18 @@ def _telegram_alert_context_receipt_from_consumer_binding( "lane": "metadata_feedback_controlled_writeback", "status": "ready_for_ai_loop_context", "metadata_only": True, - "target_write_performed": True, + "target_write_performed": binding.get("target_write_performed") is True, + "consumer_write_receipt": { + "consumer_receipt_id": source_receipt_id, + "status": str(consumer_receipt.get("status") or ""), + "target_context_receipt_write_performed": ( + consumer_receipt.get("target_context_receipt_write_performed") is True + ), + "runtime_target_write_performed": ( + consumer_receipt.get("runtime_target_write_performed") is True + ), + }, + "verifier_write_performed": False, "raw_payload_included": False, "target_selector": { "project_id": project_id, diff --git a/apps/api/src/services/telegram_alert_learning_context_post_apply_verifier.py b/apps/api/src/services/telegram_alert_learning_context_post_apply_verifier.py index 5b4d84f7a..51904a489 100644 --- a/apps/api/src/services/telegram_alert_learning_context_post_apply_verifier.py +++ b/apps/api/src/services/telegram_alert_learning_context_post_apply_verifier.py @@ -18,19 +18,34 @@ from src.services.ai_agent_log_controlled_writeback_consumer_readback import ( SCHEMA_VERSION = "telegram_alert_learning_context_post_apply_verifier_v1" DEFAULT_PROJECT_ID = "awoooi" _TARGETS = ("km", "rag", "playbook", "mcp", "verifier", "ai_agent") -_REQUIRED_TARGET_SELECTOR_FIELDS = ( +_ALERT_CARD_RECEIPT_KIND = "alert_card_learning_registry" +_CONSUMER_RECEIPT_KIND = "log_controlled_writeback_consumer" +_ALERT_CARD_TARGET_SELECTOR_FIELDS = ( "project_id", "run_id", "message_id", "target", "source_ref", ) -_REQUIRED_CHECKS = ( +_CONSUMER_TARGET_SELECTOR_FIELDS = ( + "project_id", + "dispatch_receipt_id", + "consumer_receipt_id", + "target", + "source_ref", +) +_ALERT_CARD_REQUIRED_CHECKS = ( "ai_alert_card_delivery_receipt_present", "learning_registry_binding_ready", "metadata_only_raw_payload_absent", "ai_agent_context_ref_present", ) +_CONSUMER_REQUIRED_CHECKS = ( + "consumer_binding_ready", + "consumer_apply_receipt_present", + "metadata_only_raw_payload_absent", + "post_apply_verifier_refs_present", +) async def load_latest_telegram_alert_learning_context_post_apply_verifier( @@ -163,6 +178,11 @@ def build_telegram_alert_learning_context_post_apply_verifier( "post_apply_verifier_ref_count": sum( len(item["post_apply_verifier_refs"]) for item in verifier_results ), + "verified_target_context_write_count": sum( + 1 + for item in verifier_results + if item["verified_target_context_write_performed"] is True + ), "metadata_only_receipt_count": sum( 1 for item in verifier_results if item["metadata_only"] is True ), @@ -200,27 +220,54 @@ def build_telegram_alert_learning_context_post_apply_verifier( def _verify_context_receipt(receipt: dict[str, Any]) -> dict[str, Any]: target = str(receipt.get("target") or "") + receipt_kind = _receipt_kind(receipt) + receipt_kind_supported = receipt_kind in { + _ALERT_CARD_RECEIPT_KIND, + _CONSUMER_RECEIPT_KIND, + } + consumer_receipt = receipt_kind == _CONSUMER_RECEIPT_KIND target_selector = _dict(receipt.get("target_selector")) source_diff = _dict(receipt.get("source_of_truth_diff")) check_mode = _dict(receipt.get("check_mode")) rollback = _dict(receipt.get("rollback")) post_apply_verifier = _dict(receipt.get("post_apply_verifier")) verifier_refs = _strings(post_apply_verifier.get("verifier_refs")) + required_target_selector_fields = ( + _CONSUMER_TARGET_SELECTOR_FIELDS + if consumer_receipt + else _ALERT_CARD_TARGET_SELECTOR_FIELDS + ) + required_checks = ( + _CONSUMER_REQUIRED_CHECKS + if consumer_receipt + else _ALERT_CARD_REQUIRED_CHECKS + ) target_selector_missing = [ - key for key in _REQUIRED_TARGET_SELECTOR_FIELDS if not target_selector.get(key) + key for key in required_target_selector_fields if not target_selector.get(key) ] checks = _strings(check_mode.get("checks")) - missing_checks = [check for check in _REQUIRED_CHECKS if check not in checks] + missing_checks = [check for check in required_checks if check not in checks] + source_diff_verified = _source_diff_verified( + source_diff, + target, + receipt_kind=receipt_kind, + ) + target_write_contract_verified = _target_write_contract_verified( + receipt, + receipt_kind=receipt_kind, + ) failed_checks: list[str] = [] if target not in _TARGETS: failed_checks.append("target_in_allowed_set") + if not receipt_kind_supported: + failed_checks.append("receipt_kind_supported") if receipt.get("status") != "ready_for_ai_loop_context": failed_checks.append("receipt_ready_for_ai_loop_context") if target_selector_missing: failed_checks.append("target_selector_required_fields_present") - if not _source_diff_verified(source_diff, target): + if not source_diff_verified: failed_checks.append("source_of_truth_diff_verified") if check_mode.get("enabled") is not True or missing_checks: failed_checks.append("check_mode_required_checks_present") @@ -232,13 +279,21 @@ def _verify_context_receipt(receipt: dict[str, Any]) -> dict[str, Any]: failed_checks.append("metadata_only_receipt") if receipt.get("raw_payload_included") is not False: failed_checks.append("raw_payload_absent") - if receipt.get("target_write_performed") is not False: - failed_checks.append("target_write_not_performed_by_verifier") + target_write_check = ( + "consumer_write_receipt_verified" + if consumer_receipt + else "target_write_not_performed_by_verifier" + ) + if not target_write_contract_verified: + failed_checks.append(target_write_check) + if receipt.get("verifier_write_performed") is True: + failed_checks.append("verifier_write_not_performed") if not receipt.get("ai_agent_context_ref"): failed_checks.append("ai_agent_context_ref_present") passed_checks = [ "target_in_allowed_set", + "receipt_kind_supported", "receipt_ready_for_ai_loop_context", "target_selector_required_fields_present", "source_of_truth_diff_verified", @@ -247,7 +302,8 @@ def _verify_context_receipt(receipt: dict[str, Any]) -> dict[str, Any]: "post_apply_verifier_refs_present", "metadata_only_receipt", "raw_payload_absent", - "target_write_not_performed_by_verifier", + target_write_check, + "verifier_write_not_performed", "ai_agent_context_ref_present", ] passed_checks = [check for check in passed_checks if check not in failed_checks] @@ -258,6 +314,7 @@ def _verify_context_receipt(receipt: dict[str, Any]) -> dict[str, Any]: "source_run_id": str(receipt.get("source_run_id") or ""), "source_message_id": str(receipt.get("source_message_id") or ""), "work_item_id": str(receipt.get("work_item_id") or "CIR-P0-TG-001"), + "receipt_kind": receipt_kind, "target": target, "consumer_surface": str(receipt.get("consumer_surface") or ""), "verifier_status": "passed" if not failed_checks else "failed", @@ -265,13 +322,19 @@ def _verify_context_receipt(receipt: dict[str, Any]) -> dict[str, Any]: "failed_checks": failed_checks, "target_selector_verified": not target_selector_missing, "target_selector_missing_fields": target_selector_missing, - "source_of_truth_diff_verified": _source_diff_verified(source_diff, target), + "source_of_truth_diff_verified": source_diff_verified, "check_mode_verified": check_mode.get("enabled") is True and not missing_checks, "check_mode_missing_checks": missing_checks, "rollback_verified": ( rollback.get("required") is True and bool(rollback.get("rollback_ref")) ), "post_apply_verifier_refs": verifier_refs, + "target_write_contract_verified": target_write_contract_verified, + "verified_target_context_write_performed": ( + receipt.get("target_write_performed") is True + and target_write_contract_verified + ), + "verifier_write_performed": False, "metadata_only": receipt.get("metadata_only") is True, "raw_payload_included": receipt.get("raw_payload_included") is True, "runtime_target_write_performed": False, @@ -310,7 +373,36 @@ def _active_blockers( return blockers -def _source_diff_verified(source_diff: dict[str, Any], target: str) -> bool: +def _receipt_kind(receipt: Mapping[str, Any]) -> str: + explicit = str(receipt.get("receipt_kind") or "") + if explicit: + return explicit + receipt_id = str(receipt.get("receipt_id") or "") + target_selector = _dict(receipt.get("target_selector")) + if receipt_id.startswith("telegram_alert_learning_context_fallback::") or ( + target_selector.get("dispatch_receipt_id") + and target_selector.get("consumer_receipt_id") + ): + return _CONSUMER_RECEIPT_KIND + return _ALERT_CARD_RECEIPT_KIND + + +def _source_diff_verified( + source_diff: dict[str, Any], + target: str, + *, + receipt_kind: str, +) -> bool: + if receipt_kind == _CONSUMER_RECEIPT_KIND: + return bool( + source_diff.get("current_state") + == "telegram_alert_registry_unavailable_or_not_ready" + and source_diff.get("desired_state") + == "ai_loop_agent_context_receipt_available" + and source_diff.get("delta_kind") + == f"log_controlled_writeback_{target}_context_fallback" + and source_diff.get("raw_payload_included") is False + ) return bool( source_diff.get("current_state") == "telegram_alert_learning_registry_readable" and source_diff.get("desired_state") @@ -320,6 +412,26 @@ def _source_diff_verified(source_diff: dict[str, Any], target: str) -> bool: ) +def _target_write_contract_verified( + receipt: Mapping[str, Any], + *, + receipt_kind: str, +) -> bool: + if receipt.get("verifier_write_performed") is True: + return False + if receipt_kind != _CONSUMER_RECEIPT_KIND: + return receipt.get("target_write_performed") is False + consumer_write_receipt = _dict(receipt.get("consumer_write_receipt")) + return bool( + receipt.get("target_write_performed") is True + and consumer_write_receipt.get("consumer_receipt_id") + and consumer_write_receipt.get("status") == "success" + and consumer_write_receipt.get("target_context_receipt_write_performed") + is True + and consumer_write_receipt.get("runtime_target_write_performed") is True + ) + + def _dict(value: Any) -> dict[str, Any]: return value if isinstance(value, dict) else {} diff --git a/apps/api/tests/test_ai_agent_log_controlled_writeback_consumer_readback_api.py b/apps/api/tests/test_ai_agent_log_controlled_writeback_consumer_readback_api.py index 044c19d59..7046b2daa 100644 --- a/apps/api/tests/test_ai_agent_log_controlled_writeback_consumer_readback_api.py +++ b/apps/api/tests/test_ai_agent_log_controlled_writeback_consumer_readback_api.py @@ -452,10 +452,57 @@ async def test_log_controlled_writeback_consumer_maps_ready_bindings_when_alert_ receipt["status"] == "ready_for_ai_loop_context" for receipt in context["context_receipts"] ) + assert all( + receipt["receipt_kind"] == "log_controlled_writeback_consumer" + and receipt["target_write_performed"] is True + and receipt["consumer_write_receipt"]["status"] == "success" + and receipt["consumer_write_receipt"][ + "target_context_receipt_write_performed" + ] + is True + and receipt["consumer_write_receipt"]["runtime_target_write_performed"] + is True + and receipt["verifier_write_performed"] is False + for receipt in context["context_receipts"] + ) assert context["operation_boundaries"]["runtime_target_write_performed"] is True assert context["operation_boundaries"]["raw_payload_included"] is False +@pytest.mark.asyncio +async def test_log_controlled_writeback_consumer_fallback_rejects_unverified_write( + monkeypatch, +): + consumer_rows = _consumer_receipt_rows() + consumer_rows[0]["runtime_target_write_performed"] = "false" + fake_db = _FakeDb(_ledger_rows(), consumer_rows) + monkeypatch.setattr( + consumer_module, + "get_db_context", + lambda project_id: _FakeContext(fake_db), + ) + + async def alert_registry_timeout(**_kwargs): + raise TimeoutError("alert registry unavailable") + + monkeypatch.setattr( + consumer_module, + "list_ai_alert_card_delivery_readback", + alert_registry_timeout, + ) + + payload = await load_latest_ai_agent_log_controlled_writeback_consumer_readback() + + assert payload["rollups"]["target_context_receipt_write_count"] == 5 + assert payload["rollups"]["runtime_target_write_performed"] is False + assert payload["operation_boundaries"]["runtime_target_write_performed"] is False + context = payload["telegram_alert_learning_context"] + assert context["status"] == ( + "blocked_telegram_alert_learning_registry_readback_unavailable" + ) + assert context["context_receipt_count"] == 0 + + def test_log_controlled_writeback_consumer_direct_rows_parse_json_text(): dispatch_row = _ledger_rows()[0] consumer_row = _consumer_receipt_rows()[0] @@ -621,6 +668,7 @@ def _assert_consumer_readback(payload: dict): if receipt["target"] == "ai_agent" ][0] assert ai_agent_receipt["status"] == "ready_for_ai_loop_context" + assert ai_agent_receipt["receipt_kind"] == "alert_card_learning_registry" assert ai_agent_receipt["work_item_id"] == "CIR-P0-TG-001" assert ai_agent_receipt["metadata_only"] is True assert ai_agent_receipt["target_write_performed"] is False diff --git a/apps/api/tests/test_telegram_alert_learning_context_post_apply_verifier_api.py b/apps/api/tests/test_telegram_alert_learning_context_post_apply_verifier_api.py index 326c69540..816108adb 100644 --- a/apps/api/tests/test_telegram_alert_learning_context_post_apply_verifier_api.py +++ b/apps/api/tests/test_telegram_alert_learning_context_post_apply_verifier_api.py @@ -80,6 +80,92 @@ def _context_receipts() -> list[dict]: ] +def _fallback_context_receipts() -> list[dict]: + targets = [ + ("km", "km_consumer_context"), + ("rag", "rag_consumer_context"), + ("playbook", "playbook_consumer_context"), + ("mcp", "mcp_consumer_context"), + ("verifier", "verifier_consumer_context"), + ("ai_agent", "ai_agent_consumer_context"), + ] + return [ + { + "receipt_kind": "log_controlled_writeback_consumer", + "receipt_id": ( + "telegram_alert_learning_context_fallback::" + f"log_controlled_writeback_consumed::{target}" + ), + "source_receipt_id": f"log_controlled_writeback_consumed::{target}", + "source_run_id": "", + "source_message_id": "", + "work_item_id": "CIR-P0-TG-001", + "target": target, + "consumer_surface": surface, + "source_ref": f"log_controlled_writeback_dispatched::{target}", + "ai_agent_context_ref": ( + "ai-agent://awoooi/log-controlled-writeback/" + f"log_controlled_writeback_consumed::{target}" + ), + "status": "ready_for_ai_loop_context", + "metadata_only": True, + "target_write_performed": True, + "consumer_write_receipt": { + "consumer_receipt_id": ( + f"log_controlled_writeback_consumed::{target}" + ), + "status": "success", + "target_context_receipt_write_performed": True, + "runtime_target_write_performed": True, + }, + "verifier_write_performed": False, + "raw_payload_included": False, + "target_selector": { + "project_id": "awoooi", + "dispatch_receipt_id": ( + f"log_controlled_writeback_dispatched::{target}" + ), + "consumer_receipt_id": ( + f"log_controlled_writeback_consumed::{target}" + ), + "target": target, + "source_ref": f"log_controlled_writeback_dispatched::{target}", + }, + "source_of_truth_diff": { + "current_state": ( + "telegram_alert_registry_unavailable_or_not_ready" + ), + "desired_state": "ai_loop_agent_context_receipt_available", + "delta_kind": ( + f"log_controlled_writeback_{target}_context_fallback" + ), + "raw_payload_included": False, + }, + "check_mode": { + "enabled": True, + "checks": [ + "consumer_binding_ready", + "consumer_apply_receipt_present", + "metadata_only_raw_payload_absent", + "post_apply_verifier_refs_present", + ], + }, + "rollback": { + "required": True, + "rollback_ref": ( + "rollback://telegram-alert-learning-context-fallback/" + f"log_controlled_writeback_consumed::{target}" + ), + }, + "post_apply_verifier": { + "required": True, + "verifier_refs": [f"post-write-verifier://{target}"], + }, + } + for target, surface in targets + ] + + def _consumer_readback(*, receipts: list[dict] | None = None) -> dict: return { "schema_version": "ai_agent_log_controlled_writeback_consumer_readback_v1", @@ -123,6 +209,7 @@ def test_telegram_alert_learning_context_post_apply_verifier_passes_receipts(): assert rollups["verified_context_receipt_count"] == 6 assert rollups["verified_target_count"] == 6 assert rollups["verified_ai_agent_context_receipt_count"] == 1 + assert rollups["verified_target_context_write_count"] == 0 assert rollups["raw_payload_included_count"] == 0 assert rollups["runtime_write_performed_count"] == 0 @@ -141,6 +228,48 @@ def test_telegram_alert_learning_context_post_apply_verifier_passes_receipts(): assert "raw Telegram payload" not in serialized +def test_telegram_alert_learning_context_post_apply_verifier_passes_fallback_receipts(): + payload = build_telegram_alert_learning_context_post_apply_verifier( + consumer_readback=_consumer_readback(receipts=_fallback_context_receipts()) + ) + + assert payload["status"] == ( + "telegram_alert_learning_context_post_apply_verified" + ) + assert payload["active_blockers"] == [] + assert payload["rollups"]["verified_context_receipt_count"] == 6 + assert payload["rollups"]["verified_target_context_write_count"] == 6 + assert { + result["receipt_kind"] for result in payload["verifier_results"] + } == {"log_controlled_writeback_consumer"} + assert all( + result["target_write_contract_verified"] is True + and result["verified_target_context_write_performed"] is True + and result["verifier_write_performed"] is False + for result in payload["verifier_results"] + ) + + +def test_telegram_alert_learning_context_post_apply_verifier_blocks_bad_fallback_receipt(): + receipts = _fallback_context_receipts() + receipts[0]["consumer_write_receipt"]["runtime_target_write_performed"] = False + + payload = build_telegram_alert_learning_context_post_apply_verifier( + consumer_readback=_consumer_readback(receipts=receipts) + ) + + assert payload["status"] == ( + "blocked_telegram_alert_learning_context_post_apply_verifier" + ) + assert payload["rollups"]["failed_context_receipt_count"] == 1 + failed = [ + result + for result in payload["verifier_results"] + if result["verifier_status"] == "failed" + ] + assert failed[0]["failed_checks"] == ["consumer_write_receipt_verified"] + + def test_telegram_alert_learning_context_post_apply_verifier_blocks_bad_receipt(): receipts = _context_receipts() receipts[0]["rollback"] = {"required": True, "rollback_ref": ""} @@ -159,6 +288,25 @@ def test_telegram_alert_learning_context_post_apply_verifier_blocks_bad_receipt( ) +def test_telegram_alert_learning_context_post_apply_verifier_rejects_unknown_kind(): + receipts = _context_receipts() + receipts[0]["receipt_kind"] = "unsupported_learning_receipt" + + payload = build_telegram_alert_learning_context_post_apply_verifier( + consumer_readback=_consumer_readback(receipts=receipts) + ) + + failed = [ + result + for result in payload["verifier_results"] + if result["verifier_status"] == "failed" + ] + assert payload["status"] == ( + "blocked_telegram_alert_learning_context_post_apply_verifier" + ) + assert failed[0]["failed_checks"] == ["receipt_kind_supported"] + + def test_telegram_alert_learning_context_post_apply_verifier_endpoint(monkeypatch): async def fake_loader(): return build_telegram_alert_learning_context_post_apply_verifier( diff --git a/docs/operations/sre-k3s-controlled-automation-work-items.snapshot.json b/docs/operations/sre-k3s-controlled-automation-work-items.snapshot.json index 2328241b2..e1338d30f 100644 --- a/docs/operations/sre-k3s-controlled-automation-work-items.snapshot.json +++ b/docs/operations/sre-k3s-controlled-automation-work-items.snapshot.json @@ -640,6 +640,8 @@ "apps/api/src/services/agent99_controlled_dispatch_ledger.py", "apps/api/src/services/agent99_telegram_lifecycle.py", "apps/api/src/services/agent99_public_receipts.py", + "apps/api/src/services/ai_agent_log_controlled_writeback_consumer_readback.py", + "apps/api/src/services/telegram_alert_learning_context_post_apply_verifier.py", "apps/api/src/repositories/knowledge_repository.py" ], "executor": "backup_restore_break_glass", @@ -822,12 +824,14 @@ "confirmed_truth": [ "source and focused tests bind typed verifier outcomes to same-run Telegram, KM, RAG, MCP, PlayBook and backup DR scorecard acknowledgements", "Backup/restore recipient-visible recovered cards name Agent99 BackupCheck as the read-only executor and backup_restore_readback_verifier as the typed independent verifier; apply remains not_applicable and the final durable closure receipt is required", - "BackupCheck learning assets are reconciled idempotently as read-only DR evidence rather than controlled repair" + "BackupCheck learning assets are reconciled idempotently as read-only DR evidence rather than controlled repair", + "the Telegram learning verifier now distinguishes alert-card registry receipts from log-controlled consumer receipts, verifies the latter only with an exact successful consumer write receipt, and keeps verifier runtime writes false" ], "runtime_gaps": [ - "no exact deployed source, production same-run receipt or recipient-visible Telegram readback is recorded for this revision" + "production exposed six fallback context receipts but the deployed verifier rejected all six under the alert-card-only contract; this source revision fixes that mismatch but still has no production same-run receipt", + "release c0afb10861cbb76bc232fad3bbeea0d675cd9b80 reached Gitea CD 5384 terminal failure without a deploy marker, so production remained on e614f061f781c6e20a3964875d7ab3c28cf91190" ], - "next_action": "deploy one exact source revision and read back one same-run Telegram/KM/RAG/MCP/PlayBook/DR closure receipt; source evidence must not be counted as runtime closure" + "next_action": "after the separately owned CI/CD blocker is repaired, integrate this exact tested receipt-contract revision once and read back one same-run Telegram/KM/RAG/MCP/PlayBook/DR closure receipt; source evidence must not be counted as runtime closure" }, { "id": "AIA-SRE-016",