feat(governance): 新增 Telegram receipt approval package
This commit is contained in:
@@ -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 }}>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user