feat(governance): 新增報表 runtime 啟動前閘門
Some checks failed
CD Pipeline / tests (push) Successful in 1m35s
Code Review / ai-code-review (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Successful in 6m19s
CD Pipeline / post-deploy-checks (push) Successful in 1m30s

This commit is contained in:
Your Name
2026-06-12 11:34:15 +08:00
parent 1da56ac56c
commit b17a28c293
17 changed files with 1602 additions and 15 deletions

View File

@@ -46,6 +46,7 @@ import {
type AiAgentProactiveOperationsContractSnapshot,
type AiAgentRedisDryRunGateSnapshot,
type AiAgentReportAutomationReviewSnapshot,
type AiAgentReportRuntimeReadinessSnapshot,
type AiAgentReportTruthActionabilityReviewSnapshot,
type AiAgentRuntimeVerifierEvidenceReviewSnapshot,
type AiAgentRuntimeWriteGateReviewSnapshot,
@@ -338,6 +339,7 @@ export function AutomationInventoryTab() {
const [postWriteVerifierPackage, setPostWriteVerifierPackage] = useState<AiAgentPostWriteVerifierPackageSnapshot | null>(null)
const [runtimeVerifierEvidenceReview, setRuntimeVerifierEvidenceReview] = useState<AiAgentRuntimeVerifierEvidenceReviewSnapshot | null>(null)
const [reportAutomationReview, setReportAutomationReview] = useState<AiAgentReportAutomationReviewSnapshot | null>(null)
const [reportRuntimeReadiness, setReportRuntimeReadiness] = useState<AiAgentReportRuntimeReadinessSnapshot | null>(null)
const [reportTruthActionabilityReview, setReportTruthActionabilityReview] = useState<AiAgentReportTruthActionabilityReviewSnapshot | null>(null)
const [ownerDryRunPackage, setOwnerDryRunPackage] = useState<AiAgentOwnerApprovedFixtureDryRunSnapshot | null>(null)
const [hostStatefulInventory, setHostStatefulInventory] = useState<AiAgentHostStatefulVersionInventorySnapshot | null>(null)
@@ -371,6 +373,7 @@ export function AutomationInventoryTab() {
apiClient.getAiAgentPostWriteVerifierPackage(),
apiClient.getAiAgentRuntimeVerifierEvidenceReview(),
apiClient.getAiAgentReportAutomationReview(),
apiClient.getAiAgentReportRuntimeReadiness(),
apiClient.getAiAgentReportTruthActionabilityReview(),
apiClient.getAiAgentOwnerApprovedFixtureDryRun(),
apiClient.getAiAgentHostStatefulVersionInventory(),
@@ -403,6 +406,7 @@ export function AutomationInventoryTab() {
postWriteVerifierPackageResult,
runtimeVerifierEvidenceReviewResult,
reportAutomationReviewResult,
reportRuntimeReadinessResult,
reportTruthActionabilityReviewResult,
ownerDryRunPackageResult,
hostStatefulInventoryResult,
@@ -432,6 +436,7 @@ export function AutomationInventoryTab() {
setPostWriteVerifierPackage(postWriteVerifierPackageResult.status === 'fulfilled' ? postWriteVerifierPackageResult.value : null)
setRuntimeVerifierEvidenceReview(runtimeVerifierEvidenceReviewResult.status === 'fulfilled' ? runtimeVerifierEvidenceReviewResult.value : null)
setReportAutomationReview(reportAutomationReviewResult.status === 'fulfilled' ? reportAutomationReviewResult.value : null)
setReportRuntimeReadiness(reportRuntimeReadinessResult.status === 'fulfilled' ? reportRuntimeReadinessResult.value : null)
setReportTruthActionabilityReview(reportTruthActionabilityReviewResult.status === 'fulfilled' ? reportTruthActionabilityReviewResult.value : null)
setOwnerDryRunPackage(ownerDryRunPackageResult.status === 'fulfilled' ? ownerDryRunPackageResult.value : null)
setHostStatefulInventory(hostStatefulInventoryResult.status === 'fulfilled' ? hostStatefulInventoryResult.value : null)
@@ -459,6 +464,7 @@ export function AutomationInventoryTab() {
postWriteVerifierPackageResult,
runtimeVerifierEvidenceReviewResult,
reportAutomationReviewResult,
reportRuntimeReadinessResult,
reportTruthActionabilityReviewResult,
ownerDryRunPackageResult,
hostStatefulInventoryResult,
@@ -864,6 +870,36 @@ export function AutomationInventoryTab() {
.slice(0, 5)
}, [reportAutomationReview])
const visibleReportRuntimeLanes = useMemo(() => {
if (!reportRuntimeReadiness) return []
const priority = { blocked_by_runtime_gate: 0, ready_for_owner_review: 1 } as Record<string, number>
return [...reportRuntimeReadiness.runtime_lanes]
.sort((a, b) => {
const left = priority[a.contract_status] ?? 2
const right = priority[b.contract_status] ?? 2
if (left !== right) return left - right
return a.lane_id.localeCompare(b.lane_id)
})
.slice(0, 7)
}, [reportRuntimeReadiness])
const visibleReportRuntimeDecisions = useMemo(() => {
if (!reportRuntimeReadiness) return []
const statusPriority = { approval_required: 0, blocked_by_runtime_gate: 1, ready_for_review: 2 } as Record<string, number>
const riskPriority = { critical: 0, high: 1, medium: 2, low: 3 } as Record<string, number>
return [...reportRuntimeReadiness.operator_decisions]
.sort((a, b) => {
const left = statusPriority[a.status] ?? 3
const right = statusPriority[b.status] ?? 3
if (left !== right) return left - right
const leftRisk = riskPriority[a.risk_tier] ?? 4
const rightRisk = riskPriority[b.risk_tier] ?? 4
if (leftRisk !== rightRisk) return leftRisk - rightRisk
return a.decision_id.localeCompare(b.decision_id)
})
.slice(0, 7)
}, [reportRuntimeReadiness])
const visibleReportTruthFindings = useMemo(() => {
if (!reportTruthActionabilityReview) return []
const priority = { critical: 0, high: 1, medium: 2, low: 3 } as Record<string, number>
@@ -1083,7 +1119,7 @@ export function AutomationInventoryTab() {
)
}
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !postWriteVerifierPackage || !runtimeVerifierEvidenceReview || !reportAutomationReview || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !postWriteVerifierPackage || !runtimeVerifierEvidenceReview || !reportAutomationReview || !reportRuntimeReadiness || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
return (
<div style={{ padding: 20 }}>
<GlassCard variant="subtle" padding="lg">
@@ -1232,6 +1268,16 @@ export function AutomationInventoryTab() {
const reportAutoEnabled = reportAutomationReview.rollups.current_auto_execution_enabled_count
const reportLiveDelivery = reportAutomationReview.rollups.live_report_delivery_count
const reportLiveOptimization = reportAutomationReview.rollups.live_auto_optimization_count
const reportRuntimeOverall = reportRuntimeReadiness.program_status.overall_completion_percent
const reportRuntimeLanes = reportRuntimeReadiness.rollups.runtime_lane_count
const reportRuntimeReady = reportRuntimeReadiness.rollups.ready_contract_count
const reportRuntimeBlocked = reportRuntimeReadiness.rollups.blocked_contract_count
const reportRuntimeApprovals = reportRuntimeReadiness.rollups.approval_required_decision_ids.length
const reportRuntimeEnabled = reportRuntimeReadiness.rollups.current_enabled_count
const reportRuntimeLiveDelivery = reportRuntimeReadiness.rollups.live_report_delivery_count
const reportRuntimeQueueWrites = reportRuntimeReadiness.rollups.telegram_gateway_queue_write_count
const reportRuntimeAiRuns = reportRuntimeReadiness.rollups.live_ai_analysis_count
const reportRuntimeMediumLowRuns = reportRuntimeReadiness.rollups.live_medium_low_auto_execution_count
const reportTruthOverall = reportTruthActionabilityReview.program_status.overall_completion_percent
const reportTruthFindings = reportTruthActionabilityReview.rollups.zero_signal_finding_count
const reportTruthCritical = reportTruthActionabilityReview.rollups.critical_finding_count
@@ -1997,6 +2043,132 @@ export function AutomationInventoryTab() {
</div>
</div>
<div style={{ padding: 12, border: '0.5px solid #b9d0dd', borderRadius: 7, background: '#f8fcff', display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0 }}>
<Route size={14} style={{ color: '#0284c7' }} />
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
{t('reportRuntimeReadiness.title')}
</span>
</div>
<Chip
value={t('reportRuntimeReadiness.source', {
generated: formatDateTime(reportRuntimeReadiness.generated_at),
current: reportRuntimeReadiness.program_status.current_task_id,
next: reportRuntimeReadiness.program_status.next_task_id,
})}
muted
/>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(128px, 1fr))', gap: 10 }} className="automation-inventory-live-read-kpi-grid">
<MetricCard label={t('reportRuntimeReadiness.metrics.overall')} value={`${reportRuntimeOverall}%`} tone="warn" icon={<Gauge size={16} />} />
<MetricCard label={t('reportRuntimeReadiness.metrics.lanes')} value={reportRuntimeLanes} tone="warn" icon={<Route size={16} />} />
<MetricCard label={t('reportRuntimeReadiness.metrics.ready')} value={reportRuntimeReady} tone="ok" icon={<ShieldCheck size={16} />} />
<MetricCard label={t('reportRuntimeReadiness.metrics.blocked')} value={reportRuntimeBlocked} tone={reportRuntimeBlocked > 0 ? 'danger' : 'ok'} icon={<ShieldAlert size={16} />} />
<MetricCard label={t('reportRuntimeReadiness.metrics.approvals')} value={reportRuntimeApprovals} tone={reportRuntimeApprovals > 0 ? 'danger' : 'ok'} icon={<Lock size={16} />} />
<MetricCard label={t('reportRuntimeReadiness.metrics.enabled')} value={reportRuntimeEnabled} tone={reportRuntimeEnabled === 0 ? 'warn' : 'ok'} icon={<Database size={16} />} />
<MetricCard label={t('reportRuntimeReadiness.metrics.delivery')} value={reportRuntimeLiveDelivery} tone={reportRuntimeLiveDelivery === 0 ? 'warn' : 'ok'} icon={<BellRing size={16} />} />
<MetricCard label={t('reportRuntimeReadiness.metrics.mediumLow')} value={reportRuntimeMediumLowRuns} tone={reportRuntimeMediumLowRuns === 0 ? 'warn' : 'ok'} icon={<Target size={16} />} />
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: 12 }} className="automation-inventory-live-read-grid">
<div style={{ padding: 11, border: '0.5px solid #d8e8f1', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
<SmallLabel>{t('reportRuntimeReadiness.truthTitle')}</SmallLabel>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#64727a', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
{reportRuntimeReadiness.activation_truth.truth_note}
</span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t('reportRuntimeReadiness.flags.scheduler', { value: String(reportRuntimeReadiness.activation_truth.report_scheduler_contract_ready) })} />
<Chip value={t('reportRuntimeReadiness.flags.queue', { value: reportRuntimeQueueWrites })} muted />
<Chip value={t('reportRuntimeReadiness.flags.aiRuns', { value: reportRuntimeAiRuns })} muted />
<Chip value={t('reportRuntimeReadiness.flags.mediumLow', { value: reportRuntimeMediumLowRuns })} muted />
</div>
</div>
<div style={{ padding: 11, border: '0.5px solid #d8e8f1', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
<SmallLabel>{t('reportRuntimeReadiness.telegramTitle')}</SmallLabel>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#64727a', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
{t('reportRuntimeReadiness.telegramSummary', {
room: reportRuntimeReadiness.telegram_route_readiness.canonical_room,
secret: reportRuntimeReadiness.telegram_route_readiness.secret_ref,
blocked: reportRuntimeReadiness.telegram_route_readiness.blocked_route_count,
})}
</span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t('reportRuntimeReadiness.flags.gateway', { value: String(reportRuntimeReadiness.telegram_route_readiness.gateway_required) })} />
<Chip value={t('reportRuntimeReadiness.flags.directApi', { value: String(reportRuntimeReadiness.telegram_route_readiness.direct_bot_api_allowed) })} muted />
<Chip value={t('reportRuntimeReadiness.flags.deliveryVerified', { value: String(reportRuntimeReadiness.telegram_route_readiness.e2e_delivery_verified) })} muted />
</div>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
{visibleReportRuntimeLanes.map(lane => {
const tone = lane.contract_status === 'blocked_by_runtime_gate' ? 'danger' : lane.risk_tier === 'high' || lane.risk_tier === 'critical' ? 'warn' : 'ok'
return (
<div key={lane.lane_id} style={{ padding: 10, border: '0.5px solid #d8e8f1', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 7, minWidth: 0 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center', minWidth: 0 }}>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', overflowWrap: 'anywhere' }}>
{lane.display_name}
</span>
<Chip value={redisDryRunValueLabel('agents', lane.owner_agent)} muted />
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t(`reportRuntimeReadiness.statuses.${lane.contract_status}` as never)} muted={tone !== 'danger'} />
<Chip value={t(`reportRuntimeReadiness.riskTiers.${lane.risk_tier}` as never)} muted={tone === 'ok'} />
<Chip value={t('reportRuntimeReadiness.labels.liveCount', { count: lane.current_live_count_24h })} muted />
</div>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#64727a', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{lane.enablement_required.slice(0, 2).join(' / ')}
</span>
<Chip value={lane.target_runtime} muted />
</div>
)
})}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
{reportRuntimeReadiness.automation_policies.map(policy => (
<div key={policy.risk_id} style={{ padding: 10, border: '0.5px solid #d8e8f1', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 7, minWidth: 0 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center' }}>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413' }}>
{policy.display_name}
</span>
<Chip value={t('reportRuntimeReadiness.labels.enabled', { value: String(policy.current_execution_enabled) })} muted />
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t('reportRuntimeReadiness.labels.autoAfterGuard', { value: String(policy.auto_allowed_after_guard) })} muted={!policy.auto_allowed_after_guard} />
<Chip value={t('reportRuntimeReadiness.labels.approvalRequired', { value: String(policy.approval_required) })} muted={!policy.approval_required} />
</div>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#64727a', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{policy.reporting_rule}
</span>
</div>
))}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
{visibleReportRuntimeDecisions.map(decision => (
<div key={decision.decision_id} style={{ padding: 10, border: '0.5px solid #d8e8f1', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 7, minWidth: 0 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center', minWidth: 0 }}>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', overflowWrap: 'anywhere' }}>
{decision.display_name}
</span>
<Chip value={redisDryRunValueLabel('agents', decision.owner_agent)} muted />
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t(`reportRuntimeReadiness.statuses.${decision.status}` as never)} />
<Chip value={t('reportRuntimeReadiness.labels.approvalRequired', { value: String(decision.approval_required) })} muted={!decision.approval_required} />
</div>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#64727a', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{decision.next_safe_step}
</span>
</div>
))}
</div>
</div>
<div style={{ padding: 12, border: '0.5px solid #d8c6a6', borderRadius: 7, background: '#fffdf7', display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0 }}>