feat(awooop): surface durable learning proof

This commit is contained in:
Your Name
2026-07-19 06:08:41 +08:00
parent 56e36c472c
commit 70112d7f84
3 changed files with 275 additions and 30 deletions

View File

@@ -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."
}
}
}
},

View File

@@ -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 卡點。"
}
}
}
},

View File

@@ -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({
<div className="min-w-0 border border-[#e5e0d5] bg-white px-3 py-3">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<p className="truncate text-xs font-semibold text-[#77736a]">{label}</p>
<p className="truncate text-xs font-semibold text-[#77736a]">
{label}
</p>
<p className="mt-1 whitespace-nowrap font-mono text-lg font-semibold text-[#141413]">
{value}
</p>
@@ -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]",
)}
>
<Icon className="h-4 w-4" aria-hidden="true" />
</span>
</div>
<p className="mt-2 line-clamp-2 text-xs leading-5 text-[#5f5b52]">{detail}</p>
<p className="mt-2 line-clamp-2 text-xs leading-5 text-[#5f5b52]">
{detail}
</p>
</div>
);
}
export default function AwoooPAlertsPage() {
const t = useTranslations("awooop.alerts.aiLoop");
const [coverage, setCoverage] = useState<TelegramMonitoringCoveragePayload | null>(null);
const [coverage, setCoverage] =
useState<TelegramMonitoringCoveragePayload | null>(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() {
</p>
</div>
</div>
<nav className="flex flex-wrap items-center gap-2" aria-label={t("title")}>
<nav
className="flex flex-wrap items-center gap-2"
aria-label={t("title")}
>
{NAV_ITEMS.map((item) => {
const Icon = item.icon;
return (
@@ -288,20 +358,27 @@ export default function AwoooPAlertsPage() {
)}
>
<CheckCircle2 className="h-3.5 w-3.5" aria-hidden="true" />
{coverageLoading ? t("states.loading") : ready ? t("states.ready") : t("states.actioning")}
{coverageLoading
? t("states.loading")
: ready
? t("states.ready")
: t("states.actioning")}
</span>
<span className="font-mono text-xs text-[#77736a]">
{coverage?.status ?? "telegram_alert_monitoring_coverage_readback"}
{coverage?.status ??
"telegram_alert_monitoring_coverage_readback"}
</span>
</div>
<p className="mt-1 text-xs leading-5 text-[#5f5b52]">
{t("operatorAnswer", {
manual: answer.manual_default_terminal_state_allowed === false
? t("proof.off")
: t("proof.review"),
breakGlass: answer.critical_break_glass_exception_only === true
? t("proof.criticalOnly")
: t("proof.review"),
manual:
answer.manual_default_terminal_state_allowed === false
? t("proof.off")
: t("proof.review"),
breakGlass:
answer.critical_break_glass_exception_only === true
? t("proof.criticalOnly")
: t("proof.review"),
})}
</p>
</div>
@@ -311,7 +388,10 @@ export default function AwoooPAlertsPage() {
disabled={coverageLoading}
className="inline-flex items-center gap-2 border border-[#d8d3c7] bg-white px-3 py-2 text-xs font-semibold text-[#141413] hover:border-[#d97757] disabled:opacity-60"
>
<RefreshCw className={cn("h-4 w-4", coverageLoading && "animate-spin")} aria-hidden="true" />
<RefreshCw
className={cn("h-4 w-4", coverageLoading && "animate-spin")}
aria-hidden="true"
/>
{t("refresh")}
</button>
</div>
@@ -330,6 +410,106 @@ export default function AwoooPAlertsPage() {
</div>
</section>
<section
data-testid="telegram-alert-durable-learning-proof"
className="border border-[#e0ddd4] bg-white"
>
<div className="flex flex-wrap items-start justify-between gap-3 border-b border-[#e0ddd4] px-4 py-3">
<div className="flex min-w-0 items-start gap-3">
<span
className={cn(
"flex h-9 w-9 shrink-0 items-center justify-center border",
durableLearningReady
? "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]"
: "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]",
)}
>
{durableLearningReady ? (
<ShieldCheck className="h-4 w-4" aria-hidden="true" />
) : (
<TriangleAlert className="h-4 w-4" aria-hidden="true" />
)}
</span>
<div className="min-w-0">
<h2 className="text-sm font-semibold text-[#141413]">
{t("learningProof.title")}
</h2>
<p className="mt-0.5 text-xs leading-5 text-[#5f5b52]">
{t("learningProof.subtitle")}
</p>
</div>
</div>
<span
data-testid="telegram-alert-durable-learning-state"
className={cn(
"inline-flex items-center border px-2 py-1 text-xs font-semibold",
durableLearningReady
? "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]"
: "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]",
)}
>
{durableLearningReady
? t("learningProof.states.verified")
: Number(summary.ai_loop_context_receipt_count ?? 0) > 0
? t("learningProof.states.candidate")
: t("learningProof.states.blocked")}
</span>
</div>
<div className="grid gap-3 p-4 lg:grid-cols-[minmax(0,1.4fr)_minmax(260px,0.6fr)]">
<div className="grid gap-2 sm:grid-cols-3">
{[
{
testId: "durable-learning-write-count",
label: t("learningProof.metrics.durableWrites"),
value: `${formatNumber(summary.verified_target_context_write_count)} / ${LEARNING_TARGET_COUNT}`,
},
{
testId: "durable-learning-target-count",
label: t("learningProof.metrics.verifiedTargets"),
value: `${formatNumber(summary.verified_target_count)} / ${LEARNING_TARGET_COUNT}`,
},
{
testId: "durable-learning-agent-count",
label: t("learningProof.metrics.aiAgentReceipts"),
value: formatNumber(
summary.verified_ai_agent_context_receipt_count,
),
},
].map((metric) => (
<div
key={metric.testId}
data-testid={metric.testId}
className="border border-[#e5e0d5] bg-[#faf9f3] px-3 py-3"
>
<p className="text-xs font-semibold text-[#77736a]">
{metric.label}
</p>
<p className="mt-1 font-mono text-xl font-semibold text-[#141413]">
{metric.value}
</p>
</div>
))}
</div>
<div className="min-w-0 border border-[#e5e0d5] bg-[#faf9f3] px-3 py-3">
<p className="text-xs font-semibold text-[#77736a]">
{t("learningProof.source")}
</p>
<p className="mt-1 text-sm font-semibold text-[#141413]">
{aiLoopSourceLabel}
</p>
<p className="mt-3 text-xs font-semibold text-[#77736a]">
{t("learningProof.blocker")}
</p>
<p
data-testid="telegram-alert-durable-learning-blocker"
className="mt-1 break-words text-xs leading-5 text-[#5f5b52]"
>
{learningBlockerLabel}
</p>
</div>
</div>
</section>
<section className="grid gap-3 md:grid-cols-3">
{NAV_ITEMS.map((item) => {
const Icon = item.icon;
@@ -344,13 +524,18 @@ export default function AwoooPAlertsPage() {
<Icon className="h-4 w-4" aria-hidden="true" />
</span>
<span className="min-w-0">
<span className="block truncate text-sm font-semibold">{t(item.labelKey)}</span>
<span className="block truncate text-sm font-semibold">
{t(item.labelKey)}
</span>
<span className="mt-0.5 block truncate text-xs text-[#77736a]">
{t(`details.${item.labelKey}` as never)}
</span>
</span>
</span>
<ArrowRight className="h-4 w-4 shrink-0 text-[#87867f] group-hover:text-[#d97757]" aria-hidden="true" />
<ArrowRight
className="h-4 w-4 shrink-0 text-[#87867f] group-hover:text-[#d97757]"
aria-hidden="true"
/>
</Link>
);
})}