feat(awooop): surface durable learning proof
This commit is contained in:
@@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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 卡點。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user