feat(governance): 新增 Telegram receipt approval package
All checks were successful
CD Pipeline / tests (push) Successful in 1m30s
Code Review / ai-code-review (push) Successful in 27s
CD Pipeline / build-and-deploy (push) Successful in 4m11s
CD Pipeline / post-deploy-checks (push) Successful in 1m55s

This commit is contained in:
Your Name
2026-06-11 22:11:27 +08:00
parent 7cd475581a
commit aec3657f5d
21 changed files with 982 additions and 47 deletions

View File

@@ -42,6 +42,7 @@ import {
type AiAgentLiveReadModelGateSnapshot,
type AiAgentProactiveOperationsContractSnapshot,
type AiAgentRedisDryRunGateSnapshot,
type AiAgentTelegramReceiptApprovalPackageSnapshot,
type AiProviderRouteMatrixSnapshot,
type AiAgentAutomationBacklogSnapshot,
type AiAgentAutomationInventorySnapshot,
@@ -324,6 +325,7 @@ export function AutomationInventoryTab() {
const [liveReadModelGate, setLiveReadModelGate] = useState<AiAgentLiveReadModelGateSnapshot | null>(null)
const [redisDryRunGate, setRedisDryRunGate] = useState<AiAgentRedisDryRunGateSnapshot | null>(null)
const [learningWritebackPackage, setLearningWritebackPackage] = useState<AiAgentLearningWritebackApprovalPackageSnapshot | null>(null)
const [telegramReceiptPackage, setTelegramReceiptPackage] = useState<AiAgentTelegramReceiptApprovalPackageSnapshot | null>(null)
const [hostStatefulInventory, setHostStatefulInventory] = useState<AiAgentHostStatefulVersionInventorySnapshot | null>(null)
const [serviceHealthGapMatrix, setServiceHealthGapMatrix] = useState<ServiceHealthGapMatrixSnapshot | null>(null)
const [serviceHealthNotificationPolicy, setServiceHealthNotificationPolicy] = useState<ServiceHealthFailureNotificationPolicySnapshot | null>(null)
@@ -349,6 +351,7 @@ export function AutomationInventoryTab() {
apiClient.getAiAgentLiveReadModelGate(),
apiClient.getAiAgentRedisDryRunGate(),
apiClient.getAiAgentLearningWritebackApprovalPackage(),
apiClient.getAiAgentTelegramReceiptApprovalPackage(),
apiClient.getAiAgentHostStatefulVersionInventory(),
apiClient.getServiceHealthGapMatrix(),
apiClient.getServiceHealthFailureNotificationPolicy(),
@@ -373,6 +376,7 @@ export function AutomationInventoryTab() {
liveReadModelGateResult,
redisDryRunGateResult,
learningWritebackPackageResult,
telegramReceiptPackageResult,
hostStatefulInventoryResult,
serviceHealthGapMatrixResult,
serviceHealthNotificationPolicyResult,
@@ -394,6 +398,7 @@ export function AutomationInventoryTab() {
setLiveReadModelGate(liveReadModelGateResult.status === 'fulfilled' ? liveReadModelGateResult.value : null)
setRedisDryRunGate(redisDryRunGateResult.status === 'fulfilled' ? redisDryRunGateResult.value : null)
setLearningWritebackPackage(learningWritebackPackageResult.status === 'fulfilled' ? learningWritebackPackageResult.value : null)
setTelegramReceiptPackage(telegramReceiptPackageResult.status === 'fulfilled' ? telegramReceiptPackageResult.value : null)
setHostStatefulInventory(hostStatefulInventoryResult.status === 'fulfilled' ? hostStatefulInventoryResult.value : null)
setServiceHealthGapMatrix(serviceHealthGapMatrixResult.status === 'fulfilled' ? serviceHealthGapMatrixResult.value : null)
setServiceHealthNotificationPolicy(serviceHealthNotificationPolicyResult.status === 'fulfilled' ? serviceHealthNotificationPolicyResult.value : null)
@@ -413,6 +418,7 @@ export function AutomationInventoryTab() {
liveReadModelGateResult,
redisDryRunGateResult,
learningWritebackPackageResult,
telegramReceiptPackageResult,
hostStatefulInventoryResult,
serviceHealthGapMatrixResult,
serviceHealthNotificationPolicyResult,
@@ -654,6 +660,30 @@ export function AutomationInventoryTab() {
})
}, [learningWritebackPackage])
const visibleTelegramReceiptGates = useMemo(() => {
if (!telegramReceiptPackage) return []
const priority = { approval_required: 0, blocked_by_runtime_gate: 1, contract_ready: 2 } as Record<string, number>
return [...telegramReceiptPackage.receipt_gates]
.sort((a, b) => {
const left = priority[a.status] ?? 3
const right = priority[b.status] ?? 3
if (left !== right) return left - right
return a.gate_id.localeCompare(b.gate_id)
})
}, [telegramReceiptPackage])
const visibleTelegramReceiptLanes = useMemo(() => {
if (!telegramReceiptPackage) return []
const priority = { blocked_by_runtime_gate: 0, approval_required: 1, contract_ready: 2 } as Record<string, number>
return [...telegramReceiptPackage.receipt_lanes]
.sort((a, b) => {
const left = priority[a.status] ?? 3
const right = priority[b.status] ?? 3
if (left !== right) return left - right
return a.lane_id.localeCompare(b.lane_id)
})
}, [telegramReceiptPackage])
const visibleReadinessRows = useMemo(() => {
if (!backupReadiness) return []
const priority = { blocked: 0, action_required: 1, deferred: 2, ready: 3 } as Record<string, number>
@@ -797,7 +827,7 @@ export function AutomationInventoryTab() {
)
}
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
return (
<div style={{ padding: 20 }}>
<GlassCard variant="subtle" padding="lg">
@@ -903,6 +933,12 @@ export function AutomationInventoryTab() {
const learningWritebackApprovals = learningWritebackPackage.rollups.approval_required_gate_ids.length
const learningWritebackBlockedWrites = learningWritebackPackage.rollups.blocked_write_action_count
const learningWritebackLiveWrites = learningWritebackPackage.rollups.live_write_count_total
const telegramReceiptOverall = telegramReceiptPackage.program_status.overall_completion_percent
const telegramReceiptGates = telegramReceiptPackage.rollups.receipt_gate_count
const telegramReceiptLanes = telegramReceiptPackage.rollups.receipt_lane_count
const telegramReceiptApprovals = telegramReceiptPackage.rollups.approval_required_gate_ids.length
const telegramReceiptBlockedActions = telegramReceiptPackage.rollups.blocked_runtime_action_count
const telegramReceiptLiveTotal = telegramReceiptPackage.rollups.live_receipt_count_total
const hostReadonlyDeniedCount = (
hostStatefulInventory.rollups.ssh_login_allowed_count
+ hostStatefulInventory.rollups.kubectl_command_execution_allowed_count
@@ -1792,6 +1828,111 @@ export function AutomationInventoryTab() {
</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 }}>
<BellRing size={14} style={{ color: '#d97757' }} />
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
{t('telegramReceiptPackage.title')}
</span>
</div>
<Chip
value={t('telegramReceiptPackage.source', {
generated: formatDateTime(telegramReceiptPackage.generated_at),
current: telegramReceiptPackage.program_status.current_task_id,
next: telegramReceiptPackage.program_status.next_task_id,
})}
muted
/>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, minmax(0, 1fr))', gap: 10 }} className="automation-inventory-live-read-kpi-grid">
<MetricCard label={t('telegramReceiptPackage.metrics.overall')} value={`${telegramReceiptOverall}%`} tone="warn" icon={<Gauge size={16} />} />
<MetricCard label={t('telegramReceiptPackage.metrics.gates')} value={telegramReceiptGates} tone="ok" icon={<ShieldCheck size={16} />} />
<MetricCard label={t('telegramReceiptPackage.metrics.lanes')} value={telegramReceiptLanes} tone="ok" icon={<Route size={16} />} />
<MetricCard label={t('telegramReceiptPackage.metrics.approval')} value={telegramReceiptApprovals} tone={telegramReceiptApprovals > 0 ? 'danger' : 'ok'} icon={<Lock size={16} />} />
<MetricCard label={t('telegramReceiptPackage.metrics.blocked')} value={telegramReceiptBlockedActions} tone={telegramReceiptBlockedActions > 0 ? 'danger' : 'ok'} icon={<ShieldAlert size={16} />} />
<MetricCard label={t('telegramReceiptPackage.metrics.live')} value={telegramReceiptLiveTotal} tone={telegramReceiptLiveTotal === 0 ? 'warn' : 'ok'} icon={<BellOff size={16} />} />
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 0.85fr) minmax(0, 1.15fr)', gap: 12 }} className="automation-inventory-live-read-grid">
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, minWidth: 0 }}>
<div style={{ padding: 11, border: '0.5px solid #eee9dd', borderRadius: 7, background: '#faf9f3', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
<SmallLabel>{t('telegramReceiptPackage.packageTitle')}</SmallLabel>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
{telegramReceiptPackage.receipt_package.operator_meaning}
</span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t('telegramReceiptPackage.labels.requiredFields', { count: telegramReceiptPackage.rollups.required_field_count })} />
<Chip value={t('telegramReceiptPackage.labels.forbiddenFields', { count: telegramReceiptPackage.rollups.forbidden_field_count })} muted />
<Chip value={t('telegramReceiptPackage.labels.retry', { value: String(telegramReceiptPackage.receipt_package.retry_policy_required) })} muted />
</div>
</div>
<div style={{ padding: 11, border: '0.5px solid #eee9dd', borderRadius: 7, background: '#faf9f3', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
<SmallLabel>{t('telegramReceiptPackage.truthTitle')}</SmallLabel>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
{telegramReceiptPackage.telegram_truth.truth_note}
</span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t('telegramReceiptPackage.flags.send', { value: String(telegramReceiptPackage.telegram_truth.telegram_send_allowed) })} />
<Chip value={t('telegramReceiptPackage.flags.queue', { value: String(telegramReceiptPackage.telegram_truth.gateway_queue_write_allowed) })} muted />
<Chip value={t('telegramReceiptPackage.flags.direct', { value: String(telegramReceiptPackage.telegram_truth.direct_bot_api_allowed) })} muted />
</div>
</div>
<div style={{ padding: 11, border: '0.5px solid #eee9dd', borderRadius: 7, background: '#faf9f3', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
<SmallLabel>{t('telegramReceiptPackage.redactionTitle')}</SmallLabel>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
{telegramReceiptPackage.display_redaction_contract.frontend_display_policy}
</span>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
{visibleTelegramReceiptGates.map(gate => (
<div key={gate.gate_id} style={{ padding: 10, border: '0.5px solid #eee9dd', borderRadius: 7, background: '#faf9f3', display: 'flex', flexDirection: 'column', gap: 7, minWidth: 0 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center', minWidth: 0 }}>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 11, fontWeight: 700, color: '#141413', overflowWrap: 'anywhere' }}>
{gate.gate_id}
</span>
<Chip value={redisDryRunValueLabel('statuses', gate.status)} muted={gate.status === 'contract_ready'} />
</div>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', lineHeight: 1.35 }}>
{gate.display_name}
</span>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{gate.required_evidence}
</span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={redisDryRunValueLabel('agents', gate.owner_agent)} muted />
<Chip value={gate.blocked_runtime_action} muted />
</div>
</div>
))}
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
{visibleTelegramReceiptLanes.map(lane => (
<div key={lane.lane_id} style={{ padding: 10, border: '0.5px solid #eee9dd', borderRadius: 7, background: '#faf9f3', 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('statuses', lane.status)} muted={lane.status === 'contract_ready'} />
</div>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{redisDryRunValueLabel('agents', lane.owner_agent)} · {lane.target_surface} · {lane.review_owner}
</span>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{lane.required_review}
</span>
</div>
))}
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1.15fr) minmax(0, 0.85fr)', gap: 12 }} className="automation-inventory-proof-grid">
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0 }}>
<div style={{ padding: 12, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 10, minWidth: 0 }}>

View File

@@ -292,6 +292,11 @@ export const apiClient = {
return handleResponse<AiAgentLearningWritebackApprovalPackageSnapshot>(res)
},
async getAiAgentTelegramReceiptApprovalPackage() {
const res = await fetch(`${API_BASE_URL}/agents/agent-telegram-receipt-approval-package`)
return handleResponse<AiAgentTelegramReceiptApprovalPackageSnapshot>(res)
},
async getAiAgentHostStatefulVersionInventory() {
const res = await fetch(`${API_BASE_URL}/agents/agent-host-stateful-version-inventory`)
return handleResponse<AiAgentHostStatefulVersionInventorySnapshot>(res)
@@ -1429,6 +1434,82 @@ export interface AiAgentLearningWritebackApprovalPackageSnapshot {
}
}
export interface AiAgentTelegramReceiptApprovalPackageSnapshot {
schema_version: 'ai_agent_telegram_receipt_approval_package_v1'
generated_at: string
program_status: {
overall_completion_percent: number
current_priority: 'P0' | 'P1' | 'P2' | 'P3'
current_task_id: string
next_task_id: string
read_only_mode: true
runtime_authority: 'approval_package_only_no_telegram_send'
status_note: string
}
source_refs: string[]
telegram_truth: {
telegram_send_allowed: false
gateway_queue_write_allowed: false
direct_bot_api_allowed: false
receiver_route_change_allowed: false
runtime_worker_allowed: false
live_queued_receipt_count: number
live_delivered_receipt_count: number
live_acknowledged_receipt_count: number
live_failed_receipt_count: number
live_retry_count: number
truth_note: string
}
receipt_package: {
required_fields: string[]
forbidden_fields: string[]
owner_review_required: true
retry_policy_required: true
delivery_receipt_required: true
operator_meaning: string
}
receipt_gates: Array<{
gate_id: string
display_name: string
owner_agent: 'openclaw' | 'hermes' | 'nemotron'
status: string
required_evidence: string
blocked_runtime_action: string
}>
receipt_lanes: Array<{
lane_id: string
display_name: string
target_surface: string
status: string
owner_agent: 'openclaw' | 'hermes' | 'nemotron'
review_owner: string
required_review: string
}>
retry_contract: {
retry_required: true
retry_steps: string[]
}
approval_boundaries: Record<string, false>
display_redaction_contract: {
redaction_required: true
raw_payload_display_allowed: false
private_reasoning_display_allowed: false
secret_value_display_allowed: false
allowed_frontend_content: string[]
forbidden_frontend_content: string[]
frontend_display_policy: string
}
rollups: {
receipt_gate_count: number
receipt_lane_count: number
approval_required_gate_ids: string[]
blocked_runtime_action_count: number
required_field_count: number
forbidden_field_count: number
live_receipt_count_total: number
}
}
export interface AiAgentHostStatefulVersionInventorySnapshot {
schema_version: 'ai_agent_host_stateful_version_inventory_v1'
generated_at: string