feat(sre): require Agent99 RAG and MCP closure receipts

This commit is contained in:
Your Name
2026-07-19 00:24:44 +08:00
parent f1811bf8ab
commit 17e60dd36f
5 changed files with 294 additions and 11 deletions

View File

@@ -4,13 +4,17 @@ from __future__ import annotations
import asyncio
import hashlib
import json
from collections.abc import Awaitable, Callable
from datetime import UTC, datetime
from typing import Any
from uuid import NAMESPACE_URL, uuid5
import structlog
from sqlalchemy import select, text
from sqlalchemy.dialects.postgresql import insert as pg_insert
from src.db.awooop_models import AwoooPMcpGatewayAudit
from src.db.base import get_db_context
from src.db.models import PlaybookRecord
from src.models.knowledge import (
@@ -438,6 +442,140 @@ async def _ensure_km_writeback(
return None
async def _ensure_rag_writeback(
identity: Agent99DispatchIdentity,
) -> str | None:
"""Read back the durable embedding created by the Agent99 KM writer."""
try:
async with get_db_context(identity.project_id) as db:
result = await db.execute(
text("""
SELECT id::text AS entry_id,
embedding IS NOT NULL AS embedding_persisted
FROM knowledge_entries
WHERE project_id = :project_id
AND related_incident_id = :incident_id
AND path_type = :path_type
ORDER BY created_at DESC
LIMIT 1
"""),
{
"project_id": identity.project_id,
"incident_id": identity.incident_id,
"path_type": f"agent99:{identity.run_id}",
},
)
row = result.mappings().first()
if not row or row.get("embedding_persisted") is not True:
return None
return f"knowledge_entries:{row['entry_id']}:rag_embedding_verified"
except Exception as exc:
logger.warning(
"agent99_rag_writeback_readback_failed",
run_id=str(identity.run_id),
error=str(exc),
)
return None
def _mcp_learning_call_id(identity: Agent99DispatchIdentity):
return uuid5(
NAMESPACE_URL,
f"agent99-mcp-learning:{identity.project_id}:{identity.run_id}",
)
async def _ensure_mcp_evidence_writeback(
identity: Agent99DispatchIdentity,
*,
receipt_refs: dict[str, str],
) -> str | None:
"""Publish and read back a public-safe same-run MCP evidence binding."""
required = {
"telegram_lifecycle_receipt_id",
"km_writeback_ack_id",
"rag_writeback_ack_id",
"playbook_trust_writeback_ack_id",
}
safe_refs = sanitize_agent99_public_receipt_refs(
receipt_refs,
allowed_keys=required,
)
if not required.issubset(safe_refs):
return None
bindings = {key: safe_refs[key] for key in sorted(required)}
identity_hash = hashlib.sha256(
json.dumps(
identity.public_dict(),
sort_keys=True,
separators=(",", ":"),
).encode()
).hexdigest()
bindings_hash = hashlib.sha256(
json.dumps(bindings, sort_keys=True, separators=(",", ":")).encode()
).hexdigest()
call_id = _mcp_learning_call_id(identity)
gate_result = {
"schema_version": "agent99_mcp_learning_evidence_v1",
"incident_id": identity.incident_id,
"route_id": identity.route_id,
"receipt_refs": bindings,
"raw_evidence_stored": False,
"secret_value_stored": False,
}
try:
async with get_db_context(identity.project_id) as db:
await db.execute(
pg_insert(AwoooPMcpGatewayAudit)
.values(
call_id=call_id,
project_id=identity.project_id,
run_id=identity.run_id,
trace_id=identity.trace_id,
agent_id="agent99_controlled_dispatch_reconciler",
tool_name="agent99_learning_evidence_publish",
input_hash=identity_hash,
output_hash=bindings_hash,
gate_result=gate_result,
result_status="success",
latency_ms=0,
)
.on_conflict_do_nothing(
index_elements=[AwoooPMcpGatewayAudit.call_id]
)
)
selected = await db.execute(
select(AwoooPMcpGatewayAudit).where(
AwoooPMcpGatewayAudit.call_id == call_id,
AwoooPMcpGatewayAudit.project_id == identity.project_id,
AwoooPMcpGatewayAudit.run_id == identity.run_id,
)
)
row = selected.scalar_one_or_none()
if not (
row
and str(row.trace_id or "") == identity.trace_id
and row.agent_id == "agent99_controlled_dispatch_reconciler"
and row.tool_name == "agent99_learning_evidence_publish"
and row.input_hash == identity_hash
and row.output_hash == bindings_hash
and row.result_status == "success"
and isinstance(row.gate_result, dict)
and row.gate_result == gate_result
):
return None
return f"awooop_mcp_gateway_audit:{call_id}:verified"
except Exception as exc:
logger.warning(
"agent99_mcp_learning_evidence_writeback_failed",
run_id=str(identity.run_id),
error=str(exc),
)
return None
async def _ensure_telegram_receipt(
identity: Agent99DispatchIdentity,
*,
@@ -575,10 +713,21 @@ async def _finalize_learning(
"km_writeback_ack_id",
lambda: _ensure_km_writeback(identity, mode=mode),
),
(
"rag_writeback_ack_id",
lambda: _ensure_rag_writeback(identity),
),
(
"telegram_lifecycle_receipt_id",
lambda: _ensure_telegram_receipt(identity, mode=mode),
),
(
"mcp_evidence_writeback_ack_id",
lambda: _ensure_mcp_evidence_writeback(
identity,
receipt_refs=receipt_refs,
),
),
]
if "backup_health" in identity.route_id:
assets.append((

View File

@@ -498,6 +498,8 @@ def build_agent99_dispatch_receipt_envelope(
"incident_closure_receipt_id",
"telegram_lifecycle_receipt_id",
"km_writeback_ack_id",
"rag_writeback_ack_id",
"mcp_evidence_writeback_ack_id",
"playbook_trust_writeback_ack_id",
],
},
@@ -1406,6 +1408,8 @@ class PostgresAgent99DispatchLedger:
"incident_closure_receipt_id",
"telegram_lifecycle_receipt_id",
"km_writeback_ack_id",
"rag_writeback_ack_id",
"mcp_evidence_writeback_ack_id",
"playbook_trust_writeback_ack_id",
],
},
@@ -2068,15 +2072,17 @@ class PostgresAgent99DispatchLedger:
) -> dict[str, Any]:
"""Atomically close the incident, operation log, and same-run ledger.
KM, PlayBook, DR, and Telegram each provide a durable idempotent
acknowledgement first. The incident terminal state, immutable closure
event, run state, and step-3 receipt then commit in one PostgreSQL
transaction. Redis is only a post-commit projection.
KM, RAG, MCP evidence, PlayBook, DR, and Telegram each provide a
durable idempotent acknowledgement first. The incident terminal state,
immutable closure event, run state, and step-3 receipt then commit in
one PostgreSQL transaction. Redis is only a post-commit projection.
"""
required_refs = {
"telegram_lifecycle_receipt_id",
"km_writeback_ack_id",
"rag_writeback_ack_id",
"mcp_evidence_writeback_ack_id",
"playbook_trust_writeback_ack_id",
}
if "backup_health" in identity.route_id:
@@ -2354,11 +2360,12 @@ class PostgresAgent99DispatchLedger:
) -> dict[str, Any]:
"""Checkpoint per-run learning side effects before terminal closure.
Reconciliation may crash after KM, PlayBook, Telegram, or DR succeeds.
Persisting each public acknowledgement on the same run lets the next
tick resume at the first missing asset instead of replaying completed
side effects. The asset writers retain their own deterministic keys as
the final crash fence between side effect and this checkpoint.
Reconciliation may crash after KM, RAG, MCP evidence, PlayBook,
Telegram, or DR succeeds. Persisting each public acknowledgement on
the same run lets the next tick resume at the first missing asset
instead of replaying completed side effects. The asset writers retain
their own deterministic keys as the final crash fence between side
effect and this checkpoint.
"""
supplied = sanitize_agent99_public_receipt_refs(

View File

@@ -29,6 +29,8 @@ AGENT99_LEARNING_RECEIPT_REFS = frozenset({
"incident_closure_receipt_id",
"telegram_lifecycle_receipt_id",
"km_writeback_ack_id",
"rag_writeback_ack_id",
"mcp_evidence_writeback_ack_id",
"playbook_trust_writeback_ack_id",
"dr_scorecard_writeback_ack_id",
})