diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 539ad8ba5..ad6b8cff2 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -12557,6 +12557,36 @@ "queueDetail": "No open gap", "completion": "Audit Complete", "completionDetail": "Telegram monitoring audit" + }, + "learningProof": { + "title": "Learning closure evidence", + "subtitle": "KM / RAG / MCP / PlayBook / Verifier / AI Agent closes only after the Independent Verifier confirms exactly six durable target writes.", + "source": "Evidence source", + "blocker": "Current blocker", + "states": { + "verified": "6/6 durable verified", + "candidate": "Candidate present, not verified", + "blocked": "No verifiable receipt" + }, + "metrics": { + "durableWrites": "Durable writes", + "verifiedTargets": "Verified targets", + "aiAgentReceipts": "AI Agent receipts" + }, + "sources": { + "verified": "Independent Verifier", + "metadataOnly": "Verifier has metadata only; durable writes are missing", + "consumerCandidate": "Consumer receipt candidate, not independently verified", + "alertCardCandidate": "Alert-card learning refs candidate, not independently verified", + "unavailable": "No evidence source available" + }, + "blockers": { + "loading": "Reading Independent Verifier and durable write receipts.", + "unavailable": "Coverage readback is unavailable; this must not be treated as no blocker.", + "verifier": "Independent Verifier has not passed; candidate receipts cannot replace verification.", + "durableWrites": "Exactly 6/6 durable target-write receipts have not been verified.", + "none": "No learning-closure blocker." + } } } }, diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 60818f432..12efd4ec4 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -12557,6 +12557,36 @@ "queueDetail": "沒有待補 gap", "completion": "盤點完成", "completionDetail": "Telegram monitoring audit" + }, + "learningProof": { + "title": "Learning closure 證據", + "subtitle": "只有 Independent Verifier 精確確認六個 durable target writes,才算 KM / RAG / MCP / PlayBook / Verifier / AI Agent 閉環。", + "source": "證據來源", + "blocker": "目前卡點", + "states": { + "verified": "6/6 durable verified", + "candidate": "候選存在,尚未驗證", + "blocked": "尚無可驗證 receipt" + }, + "metrics": { + "durableWrites": "Durable writes", + "verifiedTargets": "已驗證目標", + "aiAgentReceipts": "AI Agent receipts" + }, + "sources": { + "verified": "Independent Verifier", + "metadataOnly": "Verifier 僅有 metadata,尚無 durable writes", + "consumerCandidate": "Consumer receipt 候選,尚未經獨立驗證", + "alertCardCandidate": "Alert-card learning refs 候選,尚未經獨立驗證", + "unavailable": "尚無可用證據來源" + }, + "blockers": { + "loading": "正在讀取 Independent Verifier 與 durable write receipts。", + "unavailable": "Coverage readback 無法取得;不得視為沒有卡點。", + "verifier": "Independent Verifier 尚未通過;候選 receipt 不可替代驗證。", + "durableWrites": "尚未取得精確 6/6 durable target-write receipts。", + "none": "沒有 learning closure 卡點。" + } } } }, diff --git a/apps/web/src/app/[locale]/awooop/alerts/page.tsx b/apps/web/src/app/[locale]/awooop/alerts/page.tsx index 77e066371..f2c97b892 100644 --- a/apps/web/src/app/[locale]/awooop/alerts/page.tsx +++ b/apps/web/src/app/[locale]/awooop/alerts/page.tsx @@ -26,10 +26,13 @@ type TelegramMonitoringCoveragePayload = { active_blockers?: string[] | null; operator_answer?: { all_telegram_monitoring_alerts_fully_audited?: boolean | null; - all_telegram_monitoring_alert_surfaces_have_db_or_log_receipt?: boolean | null; + all_telegram_monitoring_alert_surfaces_have_db_or_log_receipt?: + | boolean + | null; all_telegram_monitoring_alerts_ai_agent_automation_ready?: boolean | null; all_known_telegram_egress_routes_controlled?: boolean | null; km_rag_mcp_playbook_ai_agent_context_ready?: boolean | null; + durable_learning_writeback_verified?: boolean | null; manual_default_terminal_state_allowed?: boolean | null; critical_break_glass_exception_only?: boolean | null; } | null; @@ -43,6 +46,12 @@ type TelegramMonitoringCoveragePayload = { runtime_km_converted_event_count_7d?: number | null; effective_ai_alert_context_receipt_total?: number | null; effective_ai_alert_context_ready_total?: number | null; + verified_target_count?: number | null; + verified_target_context_write_count?: number | null; + verified_ai_agent_context_receipt_count?: number | null; + ai_loop_context_receipt_count?: number | null; + ai_loop_context_source?: string | null; + durable_learning_writeback_ready?: boolean | null; telegram_monitoring_audit_completion_percent?: number | null; ai_controlled_gap_queue_count?: number | null; full_coverage_ready?: boolean | null; @@ -50,6 +59,7 @@ type TelegramMonitoringCoveragePayload = { }; const API_BASE = getRuntimeApiBaseUrl(); +const LEARNING_TARGET_COUNT = 6; const NAV_ITEMS = [ { @@ -98,7 +108,9 @@ function StatCard({
-

{label}

+

+ {label} +

{value}

@@ -108,20 +120,24 @@ function StatCard({ "flex h-8 w-8 shrink-0 items-center justify-center border", tone === "ok" && "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]", tone === "warn" && "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]", - tone === "neutral" && "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]", + tone === "neutral" && + "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]", )} >
-

{detail}

+

+ {detail} +

); } export default function AwoooPAlertsPage() { const t = useTranslations("awooop.alerts.aiLoop"); - const [coverage, setCoverage] = useState(null); + const [coverage, setCoverage] = + useState(null); const [coverageLoading, setCoverageLoading] = useState(true); const refreshCoverage = useCallback(async () => { @@ -137,7 +153,9 @@ export default function AwoooPAlertsPage() { }, ); if (response.ok) { - setCoverage((await response.json()) as TelegramMonitoringCoveragePayload); + setCoverage( + (await response.json()) as TelegramMonitoringCoveragePayload, + ); } } catch { setCoverage(null); @@ -157,8 +175,37 @@ export default function AwoooPAlertsPage() { const answer = coverage?.operator_answer ?? {}; const blockers = coverage?.active_blockers ?? []; const ready = - summary.full_coverage_ready === true - || coverage?.status === "telegram_alert_monitoring_coverage_ready"; + summary.full_coverage_ready === true || + coverage?.status === "telegram_alert_monitoring_coverage_ready"; + const durableLearningReady = + summary.durable_learning_writeback_ready === true && + answer.durable_learning_writeback_verified === true; + const aiLoopSource = summary.ai_loop_context_source ?? "unavailable"; + const aiLoopSourceLabel = + aiLoopSource === "post_apply_verifier" + ? t("learningProof.sources.verified") + : aiLoopSource === "post_apply_verifier_without_durable_target_writes" + ? t("learningProof.sources.metadataOnly") + : aiLoopSource === "unverified_log_controlled_writeback_consumer" + ? t("learningProof.sources.consumerCandidate") + : aiLoopSource === "unverified_ai_alert_card_learning_writeback_refs" + ? t("learningProof.sources.alertCardCandidate") + : t("learningProof.sources.unavailable"); + const learningBlocker = + blockers.find((blocker) => blocker.startsWith("telegram_alert_ai_loop_")) ?? + blockers[0] ?? + ""; + const learningBlockerLabel = coverageLoading + ? t("learningProof.blockers.loading") + : coverage == null + ? t("learningProof.blockers.unavailable") + : learningBlocker === + "telegram_alert_ai_loop_post_apply_verifier_not_ready" + ? t("learningProof.blockers.verifier") + : learningBlocker === + "telegram_alert_ai_loop_durable_learning_writeback_not_verified" + ? t("learningProof.blockers.durableWrites") + : learningBlocker || t("learningProof.blockers.none"); const surfaceValue = coverageLoading ? "-- / --" : `${formatNumber(summary.monitoring_live_evidence_received_count)} / ${formatNumber(summary.monitoring_surface_count)}`; @@ -174,7 +221,7 @@ export default function AwoooPAlertsPage() { detail: t("summary.coverageDetail", { gaps: formatNumber(summary.monitoring_live_receipt_gap_count), }), - tone: ready ? "ok" as const : "warn" as const, + tone: ready ? ("ok" as const) : ("warn" as const), icon: ShieldCheck, }, { @@ -187,7 +234,7 @@ export default function AwoooPAlertsPage() { telegram: formatNumber(summary.runtime_telegram_sent_event_count_7d), km: formatNumber(summary.runtime_km_converted_event_count_7d), }), - tone: ready ? "ok" as const : "neutral" as const, + tone: ready ? ("ok" as const) : ("neutral" as const), icon: Database, }, { @@ -195,23 +242,33 @@ export default function AwoooPAlertsPage() { label: t("summary.aiContext"), value: aiContextValue, detail: t("summary.aiContextDetail"), - tone: answer.km_rag_mcp_playbook_ai_agent_context_ready ? "ok" as const : "warn" as const, + tone: answer.km_rag_mcp_playbook_ai_agent_context_ready + ? ("ok" as const) + : ("warn" as const), icon: Bot, }, { key: "direct", label: t("summary.direct"), - value: coverageLoading ? "--" : formatNumber(summary.known_direct_send_gap_count), + value: coverageLoading + ? "--" + : formatNumber(summary.known_direct_send_gap_count), detail: t("summary.directDetail"), - tone: answer.all_known_telegram_egress_routes_controlled ? "ok" as const : "warn" as const, + tone: answer.all_known_telegram_egress_routes_controlled + ? ("ok" as const) + : ("warn" as const), icon: Send, }, { key: "queue", label: t("summary.queue"), - value: coverageLoading ? "--" : formatNumber(summary.ai_controlled_gap_queue_count), - detail: blockers[0] ?? t("summary.queueDetail"), - tone: blockers.length === 0 ? "ok" as const : "warn" as const, + value: coverageLoading + ? "--" + : formatNumber(summary.ai_controlled_gap_queue_count), + detail: blockers[0]?.startsWith("telegram_alert_ai_loop_") + ? learningBlockerLabel + : (blockers[0] ?? t("summary.queueDetail")), + tone: blockers.length === 0 ? ("ok" as const) : ("warn" as const), icon: TriangleAlert, }, { @@ -221,11 +278,21 @@ export default function AwoooPAlertsPage() { ? "--" : formatPercent(summary.telegram_monitoring_audit_completion_percent), detail: t("summary.completionDetail"), - tone: ready ? "ok" as const : "neutral" as const, + tone: ready ? ("ok" as const) : ("neutral" as const), icon: Gauge, }, ], - [answer, blockers, coverageLoading, ready, summary, surfaceValue, aiContextValue, t], + [ + answer, + blockers, + coverageLoading, + ready, + summary, + surfaceValue, + aiContextValue, + learningBlockerLabel, + t, + ], ); return ( @@ -255,7 +322,10 @@ export default function AwoooPAlertsPage() {

-