feat(governance): 新增 TG canary delivery rehearsal
Some checks failed
CD Pipeline / tests (push) Failing after 2s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
Code Review / ai-code-review (push) Failing after 2s

This commit is contained in:
Your Name
2026-06-18 08:53:53 +08:00
parent 3e30807ce3
commit 2500496fa9
12 changed files with 2518 additions and 42 deletions

View File

@@ -3721,9 +3721,14 @@ export function AutomationInventoryTab() {
+ professionalTaskExpansion.rollups.canary_delivery_bot_api_call_enabled_count
+ professionalTaskExpansion.rollups.canary_delivery_secret_read_enabled_count
+ professionalTaskExpansion.rollups.canary_delivery_paid_api_enabled_count
+ professionalTaskExpansion.rollups.canary_delivery_rehearsal_live_send_enabled_count
+ professionalTaskExpansion.rollups.canary_delivery_rehearsal_gateway_queue_write_enabled_count
+ professionalTaskExpansion.rollups.canary_delivery_rehearsal_bot_api_call_enabled_count
+ professionalTaskExpansion.rollups.canary_delivery_rehearsal_receipt_write_enabled_count
)
const professionalTaskCanarySendPacket = professionalTaskExpansion.telegram_runtime_bridge.canary_send_approval_packet
const professionalTaskCanaryDeliveryGate = professionalTaskExpansion.telegram_runtime_bridge.canary_delivery_gate
const professionalTaskCanaryDeliveryRehearsal = professionalTaskExpansion.telegram_runtime_bridge.canary_delivery_rehearsal
const professionalTaskCanaryApprovalGaps = (
professionalTaskExpansion.rollups.canary_approval_granted_count
+ professionalTaskExpansion.rollups.canary_selected_message_type_count
@@ -4178,6 +4183,129 @@ export function AutomationInventoryTab() {
</div>
</GlassCard>
<GlassCard variant="subtle" padding="md">
<div style={{ display: 'flex', flexDirection: 'column', gap: 14, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, minWidth: 0 }}>
<div style={{
width: 38,
height: 38,
borderRadius: 8,
border: '0.5px solid #19875440',
background: 'rgba(25,135,84,0.08)',
color: '#198754',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
}}>
<BellRing size={18} />
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 5, minWidth: 0 }}>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 18, fontWeight: 700, color: '#141413', lineHeight: 1.15, overflowWrap: 'anywhere' }}>
{t('reportDeliveryHero.title')}
</span>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 11, color: '#5c5a55', lineHeight: 1.55, overflowWrap: 'anywhere' }}>
{t('reportDeliveryHero.subtitle', {
current: reportLiveDeliveryApprovalPackage.program_status.current_task_id,
next: reportLiveDeliveryApprovalPackage.program_status.next_task_id,
})}
</span>
</div>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'flex-end', gap: 6, minWidth: 0 }}>
<Chip value={t('reportDeliveryHero.route', { room: 'AwoooI SRE 戰情室' })} />
<Chip value={t('reportDeliveryHero.source', {
generated: formatDateTime(reportLiveDeliveryApprovalPackage.generated_at),
current: reportLiveDeliveryApprovalPackage.program_status.current_task_id,
})} muted />
</div>
</div>
<div style={{ padding: 11, border: '0.5px solid #b7d6c4', borderRadius: 7, background: '#f8fffb', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 11, color: '#375446', lineHeight: 1.55, overflowWrap: 'anywhere' }}>
{t('reportDeliveryHero.statusLine')}
</span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t('reportDeliveryHero.flags.scheduler', { value: String(reportLiveDeliveryApprovalPackage.delivery_approval_truth.scheduler_enabled) })} muted />
<Chip value={t('reportDeliveryHero.flags.gateway', { value: String(reportLiveDeliveryApprovalPackage.delivery_approval_truth.gateway_queue_write_enabled) })} muted />
<Chip value={t('reportDeliveryHero.flags.telegram', { value: String(reportLiveDeliveryApprovalPackage.delivery_approval_truth.telegram_send_enabled) })} muted />
<Chip value={t('reportDeliveryHero.flags.botApi', { value: String(reportLiveDeliveryApprovalPackage.delivery_approval_truth.bot_api_call_enabled) })} muted />
<Chip value={t('reportDeliveryHero.flags.receipt', { value: String(reportLiveDeliveryApprovalPackage.delivery_approval_truth.report_receipt_write_enabled) })} muted />
<Chip value={t('reportDeliveryHero.flags.analysis', { value: String(reportLiveDeliveryApprovalPackage.delivery_approval_truth.ai_analysis_run_enabled) })} muted />
<Chip value={t('reportDeliveryHero.flags.ownerReview', { value: String(reportLiveDeliveryApprovalPackage.delivery_approval_truth.owner_review_required_before_delivery) })} />
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(148px, 1fr))', gap: 10 }} className="automation-inventory-live-read-kpi-grid">
<MetricCard label={t('reportDeliveryHero.metrics.packets')} value={reportDeliveryPackets} tone="warn" icon={<FileText size={16} />} />
<MetricCard label={t('reportDeliveryHero.metrics.routeGates')} value={reportDeliveryRouteGates} tone="warn" icon={<Route size={16} />} />
<MetricCard label={t('reportDeliveryHero.metrics.receipts')} value={reportDeliveryReceipts} tone="warn" icon={<Archive size={16} />} />
<MetricCard label={t('reportDeliveryHero.metrics.approvalRequired')} value={reportDeliveryApprovalRequired} tone="warn" icon={<Lock size={16} />} />
<MetricCard label={t('reportDeliveryHero.metrics.telegramSends')} value={reportDeliveryTelegramSends} tone={reportDeliveryTelegramSends === 0 ? 'ok' : 'danger'} icon={<BellRing size={16} />} />
<MetricCard label={t('reportDeliveryHero.metrics.liveWrites')} value={reportDeliveryLiveWrites} tone={reportDeliveryLiveWrites === 0 ? 'ok' : 'danger'} icon={<Database size={16} />} />
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(230px, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
{visibleReportDeliveryPackets.map(packet => (
<div key={`delivery-hero-${packet.packet_id}`} style={{ padding: 12, border: '0.5px solid #b7d6c4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 9, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, minWidth: 0 }}>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 14, fontWeight: 700, color: '#141413', overflowWrap: 'anywhere' }}>
{packet.display_name}
</span>
<Chip value={t(`reportLiveDeliveryApprovalPackage.cadences.${packet.cadence}` as never)} muted />
</div>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#5c5a55', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{packet.operator_guidance}
</span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={redisDryRunValueLabel('agents', packet.owner_agent)} muted />
<Chip value={t(`reportLiveDeliveryApprovalPackage.packetStatuses.${packet.status}` as never)} muted={packet.status === 'ready_for_owner_review'} />
<Chip value={t(`reportLiveDeliveryApprovalPackage.riskTiers.${packet.risk_tier}` as never)} muted={packet.risk_tier !== 'critical'} />
<Chip value={t('reportDeliveryHero.labels.noSendMode', { value: String(packet.no_send_mode) })} muted />
</div>
</div>
))}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: 10 }} className="automation-inventory-live-read-grid">
<div style={{ padding: 12, border: '0.5px solid #b7d6c4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 9, minWidth: 0 }}>
<SmallLabel>{t('reportDeliveryHero.routeGateTitle')}</SmallLabel>
{visibleReportRouteGates.slice(0, 3).map(gate => (
<div key={`delivery-hero-gate-${gate.gate_id}`} style={{ display: 'flex', flexDirection: 'column', gap: 5, minWidth: 0 }}>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', overflowWrap: 'anywhere' }}>
{gate.display_name}
</span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t(`reportLiveDeliveryApprovalPackage.gateStatuses.${gate.status}` as never)} muted={gate.status === 'ready_for_owner_review'} />
<Chip value={t('reportDeliveryHero.labels.requiredEvidence', { count: gate.required_evidence.length })} muted />
<Chip value={t('reportDeliveryHero.labels.gateway', { value: String(gate.gateway_queue_write_enabled) })} muted />
<Chip value={t('reportDeliveryHero.labels.telegram', { value: String(gate.telegram_send_enabled) })} muted />
</div>
</div>
))}
</div>
<div style={{ padding: 12, border: '0.5px solid #b7d6c4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 9, minWidth: 0 }}>
<SmallLabel>{t('reportDeliveryHero.receiptTitle')}</SmallLabel>
{visibleReportDeliveryReceipts.map(receipt => (
<div key={`delivery-hero-receipt-${receipt.receipt_id}`} style={{ display: 'flex', flexDirection: 'column', gap: 5, minWidth: 0 }}>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', overflowWrap: 'anywhere' }}>
{receipt.display_name}
</span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t(`reportLiveDeliveryApprovalPackage.receiptStatuses.${receipt.status}` as never)} muted={receipt.status === 'ready_for_owner_review'} />
<Chip value={t('reportDeliveryHero.labels.requiredFields', { count: receipt.required_fields.length })} muted />
<Chip value={t('reportDeliveryHero.labels.liveSendCount', { count: receipt.live_send_count })} muted />
<Chip value={t('reportDeliveryHero.labels.receiptWrite', { value: String(receipt.receipt_write_allowed) })} muted />
</div>
</div>
))}
</div>
</div>
</div>
</GlassCard>
<GlassCard variant="subtle" padding="md">
<div style={{ display: 'flex', flexDirection: 'column', gap: 13, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
@@ -4342,6 +4470,9 @@ export function AutomationInventoryTab() {
<MetricCard label={t('professionalTaskExpansion.metrics.deliveryFields')} value={professionalTaskExpansion.rollups.canary_delivery_required_field_count} tone="warn" icon={<ClipboardCheck size={16} />} />
<MetricCard label={t('professionalTaskExpansion.metrics.deliveryPreflight')} value={professionalTaskExpansion.rollups.canary_delivery_preflight_check_count} tone="warn" icon={<ShieldCheck size={16} />} />
<MetricCard label={t('professionalTaskExpansion.metrics.deliveryHoldReasons')} value={professionalTaskExpansion.rollups.canary_delivery_hold_reason_count} tone="danger" icon={<Lock size={16} />} />
<MetricCard label={t('professionalTaskExpansion.metrics.deliveryRehearsal')} value={professionalTaskExpansion.rollups.canary_delivery_rehearsal_count} tone="ok" icon={<Route size={16} />} />
<MetricCard label={t('professionalTaskExpansion.metrics.rehearsalChecks')} value={`${professionalTaskExpansion.rollups.canary_delivery_rehearsal_completed_check_count}/${professionalTaskExpansion.rollups.canary_delivery_rehearsal_readback_check_count}`} tone="ok" icon={<ClipboardCheck size={16} />} />
<MetricCard label={t('professionalTaskExpansion.metrics.rehearsalStops')} value={professionalTaskExpansion.rollups.canary_delivery_rehearsal_stop_condition_count} tone="danger" icon={<ShieldAlert size={16} />} />
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: 10 }}>
@@ -4512,6 +4643,56 @@ export function AutomationInventoryTab() {
</div>
</div>
<div style={{ padding: 10, border: '0.5px solid #a7d4bd', borderRadius: 7, background: '#f8fffb', minWidth: 0 }}>
<SmallLabel>{t('professionalTaskExpansion.canaryDeliveryRehearsalTitle')}</SmallLabel>
<p style={{ margin: '6px 0 8px', fontFamily: "'DM Mono', monospace", fontSize: 10, lineHeight: 1.5, color: '#375446', overflowWrap: 'anywhere' }}>
{professionalTaskCanaryDeliveryRehearsal.rehearsal_id} · {professionalTaskCanaryDeliveryRehearsal.status}
</p>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 8 }}>
<Chip value={t('professionalTaskExpansion.labels.rehearsalReady', { value: String(professionalTaskCanaryDeliveryRehearsal.rehearsal_ready) })} />
<Chip value={t('professionalTaskExpansion.labels.selectedMessage', { value: professionalTaskCanaryDeliveryRehearsal.selected_message_type })} muted />
<Chip value={t('professionalTaskExpansion.labels.targetRoom', { value: professionalTaskCanaryDeliveryRehearsal.target_room_env })} muted />
<Chip value={t('professionalTaskExpansion.labels.valueVisible', { value: String(professionalTaskCanaryDeliveryRehearsal.target_room_value_visible) })} muted />
<Chip value={t('professionalTaskExpansion.labels.dedup', { value: professionalTaskCanaryDeliveryRehearsal.dedup_key })} muted />
<Chip value={t('professionalTaskExpansion.labels.previewHash', { value: professionalTaskCanaryDeliveryRehearsal.sanitized_preview_hash })} muted />
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: 8 }} className="automation-inventory-live-read-grid">
<div style={{ padding: 8, borderRadius: 6, background: '#fff', border: '0.5px solid #c7e7d4', display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 }}>
<SmallLabel>{t('professionalTaskExpansion.rehearsalEnvelopeTitle')}</SmallLabel>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t('professionalTaskExpansion.labels.queueWrites', { value: String(professionalTaskCanaryDeliveryRehearsal.gateway_envelope_preview.queue_write_enabled) })} muted />
<Chip value={t('professionalTaskExpansion.labels.botCalls', { value: String(professionalTaskCanaryDeliveryRehearsal.gateway_envelope_preview.bot_api_call_enabled) })} muted />
<Chip value={t('professionalTaskExpansion.labels.send', { value: String(professionalTaskCanaryDeliveryRehearsal.gateway_envelope_preview.telegram_send_enabled) })} muted />
<Chip value={t('professionalTaskExpansion.labels.receiptWrite', { value: String(professionalTaskCanaryDeliveryRehearsal.gateway_envelope_preview.delivery_receipt_write_enabled) })} muted />
</div>
</div>
<div style={{ padding: 8, borderRadius: 6, background: '#fff', border: '0.5px solid #c7e7d4', display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 }}>
<SmallLabel>{t('professionalTaskExpansion.rehearsalReadbackTitle')}</SmallLabel>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t('professionalTaskExpansion.labels.owner', { value: professionalTaskCanaryDeliveryRehearsal.readback_drill.owner_agent })} muted />
<Chip value={t('professionalTaskExpansion.labels.completedChecks', { done: professionalTaskCanaryDeliveryRehearsal.readback_drill.completed_check_count, total: professionalTaskCanaryDeliveryRehearsal.readback_drill.required_checks.length })} />
<Chip value={t('professionalTaskExpansion.labels.failedChecks', { count: professionalTaskCanaryDeliveryRehearsal.readback_drill.failed_check_count })} muted />
<Chip value={t('professionalTaskExpansion.labels.receiptWrite', { value: String(professionalTaskCanaryDeliveryRehearsal.readback_drill.production_receipt_write_enabled) })} muted />
<Chip value={t('professionalTaskExpansion.labels.liveReadback', { value: String(professionalTaskCanaryDeliveryRehearsal.readback_drill.live_receipt_readback_enabled) })} muted />
</div>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 8, marginTop: 8 }}>
{professionalTaskCanaryDeliveryRehearsal.dry_run_steps.map(step => (
<div key={step} style={{ padding: 8, borderRadius: 6, background: '#eef8f4', fontFamily: "'DM Mono', monospace", fontSize: 10, lineHeight: 1.45, color: '#176d8a', overflowWrap: 'anywhere' }}>
{step}
</div>
))}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 8, marginTop: 8 }}>
{professionalTaskCanaryDeliveryRehearsal.stop_conditions.slice(0, 7).map(condition => (
<div key={condition} style={{ padding: 8, borderRadius: 6, background: '#fff7ed', fontFamily: "'DM Mono', monospace", fontSize: 10, lineHeight: 1.45, color: '#7a4a20', overflowWrap: 'anywhere' }}>
{t('professionalTaskExpansion.labels.stopCondition', { value: condition })}
</div>
))}
</div>
</div>
<SmallLabel>{t('professionalTaskExpansion.tasksTitle')}</SmallLabel>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
{visibleProfessionalTasks.map(task => {

View File

@@ -1429,8 +1429,8 @@ export interface AiAgentProfessionalTaskExpansionSnapshot {
program_status: {
overall_completion_percent: number
current_priority: 'P0' | 'P1' | 'P2' | 'P3'
current_task_id: 'P2-405D'
next_task_id: 'P2-405E'
current_task_id: 'P2-405E'
next_task_id: 'P2-405F'
read_only_mode: true
runtime_authority: 'professional_task_expansion_and_telegram_bridge_read_only_no_send'
status_note: string
@@ -1645,6 +1645,54 @@ export interface AiAgentProfessionalTaskExpansionSnapshot {
}
delivery_decision_log: unknown[]
}
canary_delivery_rehearsal: {
rehearsal_id: string
status: string
rehearsal_ready: boolean
selected_message_type: string
selected_preview_id: string
selected_receipt_expectation_id: string
target_room_alias: string
target_room_env: string
target_room_value_visible: boolean
dry_run_window: string
preview_hash_algorithm: string
sanitized_preview_hash: string
dedup_key: string
gateway_envelope_preview: {
message_type: string
target_room_env_ref: string
dedup_key: string
preview_hash: string
risk_tier: string
queue_write_enabled: boolean
bot_api_call_enabled: boolean
telegram_send_enabled: boolean
delivery_receipt_write_enabled: boolean
}
dry_run_steps: string[]
readback_drill: {
owner_agent: string
required_checks: string[]
completed_check_count: number
failed_check_count: number
production_receipt_write_enabled: boolean
live_receipt_readback_enabled: boolean
}
stop_conditions: string[]
rollback_mute_controls: string[]
execution_flags: {
live_delivery_enabled: boolean
gateway_queue_write_enabled: boolean
bot_api_call_enabled: boolean
telegram_send_enabled: boolean
delivery_receipt_write_enabled: boolean
production_write_enabled: boolean
secret_read_enabled: boolean
paid_api_enabled: boolean
}
next_gate: string
}
no_send_preview_completion_percent: number
canary_approval_package_completion_percent: number
canary_send_approval_packet_ready: boolean
@@ -1655,6 +1703,8 @@ export interface AiAgentProfessionalTaskExpansionSnapshot {
canary_delivery_approved: boolean
canary_delivery_attempt_allowed: boolean
canary_delivery_gate_completion_percent: number
canary_delivery_rehearsal_ready: boolean
canary_delivery_rehearsal_completion_percent: number
}
professional_task_domains: Array<{
domain_id: string
@@ -1751,6 +1801,17 @@ export interface AiAgentProfessionalTaskExpansionSnapshot {
canary_delivery_bot_api_call_enabled_count: number
canary_delivery_secret_read_enabled_count: number
canary_delivery_paid_api_enabled_count: number
canary_delivery_rehearsal_count: number
canary_delivery_rehearsal_step_count: number
canary_delivery_rehearsal_readback_check_count: number
canary_delivery_rehearsal_stop_condition_count: number
canary_delivery_rehearsal_rollback_mute_control_count: number
canary_delivery_rehearsal_completed_check_count: number
canary_delivery_rehearsal_failed_check_count: number
canary_delivery_rehearsal_live_send_enabled_count: number
canary_delivery_rehearsal_gateway_queue_write_enabled_count: number
canary_delivery_rehearsal_bot_api_call_enabled_count: number
canary_delivery_rehearsal_receipt_write_enabled_count: number
}
}