feat(governance): expose AI agent execution queue
This commit is contained in:
@@ -3611,6 +3611,80 @@
|
||||
"next": "下一步:{gates} 個 learning / trust gate 未打開。"
|
||||
}
|
||||
},
|
||||
"executionQueue": {
|
||||
"title": "全面授權後推進佇列",
|
||||
"badge": "低中風險自動準備 · 高風險等審核",
|
||||
"summary": "已排定 {rows} 條 AI Agent 工作槽;低中風險可自動準備 {auto} 件,高風險/需審核 {review} 件,阻擋 {blocked} 件。",
|
||||
"risks": {
|
||||
"lowMedium": "低/中風險",
|
||||
"medium": "中風險",
|
||||
"highCritical": "高/關鍵風險"
|
||||
},
|
||||
"modes": {
|
||||
"noWrite": "no-write 自動巡檢",
|
||||
"marketRadar": "版本雷達",
|
||||
"noSendPreview": "無發送報告預覽",
|
||||
"autoPrepare": "自動準備候選",
|
||||
"ownerReview": "owner review",
|
||||
"shadowReplay": "shadow replay",
|
||||
"learningDraft": "學習草稿"
|
||||
},
|
||||
"telegram": {
|
||||
"failureOnly": "Telegram:只在失敗/需處置時產摘要候選",
|
||||
"approvalDigest": "Telegram:批准後才可送升級摘要",
|
||||
"noSendPreview": "Telegram:只產 no-send preview",
|
||||
"actionDigest": "Telegram:產低中風險處置摘要",
|
||||
"ownerGate": "Telegram:高風險需 owner gate",
|
||||
"replayDigest": "Telegram:只產 replay 摘要",
|
||||
"learningDigest": "Telegram:只產學習摘要"
|
||||
},
|
||||
"gates": {
|
||||
"noRuntimeWrite": "runtime/host 寫入允許 {value}",
|
||||
"noPackageUpgrade": "升級/lockfile 寫入阻擋 {blocked}",
|
||||
"noTelegramLive": "Telegram live/Bot API {sends}",
|
||||
"whitelistNoLive": "低中風險正式執行總數 {live}",
|
||||
"highRiskBlocked": "高風險 runtime 阻擋 {blocked}",
|
||||
"noProviderSwitch": "provider switch / production write {writes}",
|
||||
"noLearningWrite": "learning/trust/runtime 寫入 {writes}"
|
||||
},
|
||||
"items": {
|
||||
"serviceOps": {
|
||||
"label": "服務健康與 Runtime 巡檢",
|
||||
"detail": "服務需處置 {health}、runtime 缺口 {runtime}、runner attestation {runner}。",
|
||||
"next": "自動整理 evidence packet,產 {candidates} 個 dry-run / verifier 候選。"
|
||||
},
|
||||
"versionRadar": {
|
||||
"label": "套件、工具、AI 技術版本雷達",
|
||||
"detail": "過期來源 {stale}、漂移/升級候選 {candidates}。",
|
||||
"next": "Nemotron 做 no-write 比對,blocked 操作 {blocked} 先轉 owner packet。"
|
||||
},
|
||||
"reportOps": {
|
||||
"label": "日報 / 週報 / 月報產製",
|
||||
"detail": "報告契約 ready {daily}、queue 草稿 {drafts}、圖表來源 {charts}。",
|
||||
"next": "Hermes 產 no-send 報告與圖表,{approvals} 個發送/優化決策等 gate。"
|
||||
},
|
||||
"lowMedium": {
|
||||
"label": "低中風險自動處理候選",
|
||||
"detail": "低風險 {low}、中風險 {medium}、Verifier {verifiers}。",
|
||||
"next": "OpenClaw 先乾跑與審計理由;blocked runtime action {blocked} 不執行。"
|
||||
},
|
||||
"highRisk": {
|
||||
"label": "高風險審核與批准包",
|
||||
"detail": "關鍵 {critical}、高風險 {high}、批准包 {packets}。",
|
||||
"next": "Security / OpenClaw 只產審核包;已接受 owner response {accepted}。"
|
||||
},
|
||||
"shadow": {
|
||||
"label": "Nemotron replay / shadow 評測",
|
||||
"detail": "no-write replay {replays}、blocked {blocked}、待審 {review}。",
|
||||
"next": "產 Critic scorecard;{checkpoints} 個 checkpoint 未批准前不升級。"
|
||||
},
|
||||
"learning": {
|
||||
"label": "RAG / KM / PlayBook 學習回寫",
|
||||
"detail": "匹配 approval {matched}、KM draft {km}、learning gate {gates}。",
|
||||
"next": "Hermes 整理 {writebacks} 個寫回候選;runtime write gate 未開不寫入。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"badges": {
|
||||
"readOnly": "全域只讀控管",
|
||||
"noTranscript": "只顯示脫敏治理狀態",
|
||||
|
||||
@@ -3611,6 +3611,80 @@
|
||||
"next": "下一步:{gates} 個 learning / trust gate 未打開。"
|
||||
}
|
||||
},
|
||||
"executionQueue": {
|
||||
"title": "全面授權後推進佇列",
|
||||
"badge": "低中風險自動準備 · 高風險等審核",
|
||||
"summary": "已排定 {rows} 條 AI Agent 工作槽;低中風險可自動準備 {auto} 件,高風險/需審核 {review} 件,阻擋 {blocked} 件。",
|
||||
"risks": {
|
||||
"lowMedium": "低/中風險",
|
||||
"medium": "中風險",
|
||||
"highCritical": "高/關鍵風險"
|
||||
},
|
||||
"modes": {
|
||||
"noWrite": "no-write 自動巡檢",
|
||||
"marketRadar": "版本雷達",
|
||||
"noSendPreview": "無發送報告預覽",
|
||||
"autoPrepare": "自動準備候選",
|
||||
"ownerReview": "owner review",
|
||||
"shadowReplay": "shadow replay",
|
||||
"learningDraft": "學習草稿"
|
||||
},
|
||||
"telegram": {
|
||||
"failureOnly": "Telegram:只在失敗/需處置時產摘要候選",
|
||||
"approvalDigest": "Telegram:批准後才可送升級摘要",
|
||||
"noSendPreview": "Telegram:只產 no-send preview",
|
||||
"actionDigest": "Telegram:產低中風險處置摘要",
|
||||
"ownerGate": "Telegram:高風險需 owner gate",
|
||||
"replayDigest": "Telegram:只產 replay 摘要",
|
||||
"learningDigest": "Telegram:只產學習摘要"
|
||||
},
|
||||
"gates": {
|
||||
"noRuntimeWrite": "runtime/host 寫入允許 {value}",
|
||||
"noPackageUpgrade": "升級/lockfile 寫入阻擋 {blocked}",
|
||||
"noTelegramLive": "Telegram live/Bot API {sends}",
|
||||
"whitelistNoLive": "低中風險正式執行總數 {live}",
|
||||
"highRiskBlocked": "高風險 runtime 阻擋 {blocked}",
|
||||
"noProviderSwitch": "provider switch / production write {writes}",
|
||||
"noLearningWrite": "learning/trust/runtime 寫入 {writes}"
|
||||
},
|
||||
"items": {
|
||||
"serviceOps": {
|
||||
"label": "服務健康與 Runtime 巡檢",
|
||||
"detail": "服務需處置 {health}、runtime 缺口 {runtime}、runner attestation {runner}。",
|
||||
"next": "自動整理 evidence packet,產 {candidates} 個 dry-run / verifier 候選。"
|
||||
},
|
||||
"versionRadar": {
|
||||
"label": "套件、工具、AI 技術版本雷達",
|
||||
"detail": "過期來源 {stale}、漂移/升級候選 {candidates}。",
|
||||
"next": "Nemotron 做 no-write 比對,blocked 操作 {blocked} 先轉 owner packet。"
|
||||
},
|
||||
"reportOps": {
|
||||
"label": "日報 / 週報 / 月報產製",
|
||||
"detail": "報告契約 ready {daily}、queue 草稿 {drafts}、圖表來源 {charts}。",
|
||||
"next": "Hermes 產 no-send 報告與圖表,{approvals} 個發送/優化決策等 gate。"
|
||||
},
|
||||
"lowMedium": {
|
||||
"label": "低中風險自動處理候選",
|
||||
"detail": "低風險 {low}、中風險 {medium}、Verifier {verifiers}。",
|
||||
"next": "OpenClaw 先乾跑與審計理由;blocked runtime action {blocked} 不執行。"
|
||||
},
|
||||
"highRisk": {
|
||||
"label": "高風險審核與批准包",
|
||||
"detail": "關鍵 {critical}、高風險 {high}、批准包 {packets}。",
|
||||
"next": "Security / OpenClaw 只產審核包;已接受 owner response {accepted}。"
|
||||
},
|
||||
"shadow": {
|
||||
"label": "Nemotron replay / shadow 評測",
|
||||
"detail": "no-write replay {replays}、blocked {blocked}、待審 {review}。",
|
||||
"next": "產 Critic scorecard;{checkpoints} 個 checkpoint 未批准前不升級。"
|
||||
},
|
||||
"learning": {
|
||||
"label": "RAG / KM / PlayBook 學習回寫",
|
||||
"detail": "匹配 approval {matched}、KM draft {km}、learning gate {gates}。",
|
||||
"next": "Hermes 整理 {writebacks} 個寫回候選;runtime write gate 未開不寫入。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"badges": {
|
||||
"readOnly": "全域只讀控管",
|
||||
"noTranscript": "只顯示脫敏治理狀態",
|
||||
|
||||
@@ -5455,6 +5455,156 @@ export function AutomationInventoryTab() {
|
||||
icon: <BookOpenCheck size={15} />,
|
||||
},
|
||||
]
|
||||
const globalControlQueueAutoPrepared = (
|
||||
serviceHealthActions
|
||||
+ dependencySupplyChainDriftMonitor.rollups.drift_candidate_count
|
||||
+ lowMediumWhitelistCandidates
|
||||
+ reportDryRunArtifacts
|
||||
+ runtimeShadowPassed
|
||||
+ matchedPlaybookCandidates
|
||||
)
|
||||
const globalControlQueueOwnerReview = highRiskOwnerQueueItems + reportRuntimeApprovals + runtimeShadowApprovals + learningWritebackApprovals
|
||||
const globalControlQueueBlocked = highRiskOwnerQueueBlocked + operationPermissionBlocked + runtimeShadowBlocked + reportRuntimeBlocked
|
||||
const globalControlExecutionQueueRows: Array<{
|
||||
key: string
|
||||
label: string
|
||||
owner: string
|
||||
risk: string
|
||||
mode: string
|
||||
value: string
|
||||
detail: string
|
||||
next: string
|
||||
telegram: string
|
||||
gate: string
|
||||
tone: 'ok' | 'warn' | 'danger' | 'neutral'
|
||||
icon: ReactNode
|
||||
}> = [
|
||||
{
|
||||
key: 'serviceOps',
|
||||
label: t('globalControl.executionQueue.items.serviceOps.label'),
|
||||
owner: 'OpenClaw / Hermes',
|
||||
risk: t('globalControl.executionQueue.risks.medium'),
|
||||
mode: t('globalControl.executionQueue.modes.noWrite'),
|
||||
value: String(serviceHealthActions + runtimeLiveMissing + giteaRunnerActions),
|
||||
detail: t('globalControl.executionQueue.items.serviceOps.detail', {
|
||||
health: serviceHealthActions,
|
||||
runtime: runtimeLiveMissing,
|
||||
runner: giteaRunnerActions,
|
||||
}),
|
||||
next: t('globalControl.executionQueue.items.serviceOps.next', { candidates: serviceHealthOperatorReviews + candidateDryRunNeedsReview }),
|
||||
telegram: t('globalControl.executionQueue.telegram.failureOnly'),
|
||||
gate: t('globalControl.executionQueue.gates.noRuntimeWrite', { value: serviceHealthDeniedCount + hostReadonlyDeniedCount }),
|
||||
tone: serviceHealthActions + runtimeLiveMissing + giteaRunnerActions > 0 ? 'warn' : 'ok',
|
||||
icon: <Gauge size={15} />,
|
||||
},
|
||||
{
|
||||
key: 'versionRadar',
|
||||
label: t('globalControl.executionQueue.items.versionRadar.label'),
|
||||
owner: 'Nemotron / Hermes',
|
||||
risk: t('globalControl.executionQueue.risks.medium'),
|
||||
mode: t('globalControl.executionQueue.modes.marketRadar'),
|
||||
value: String(dependencySupplyChainDriftMonitor.rollups.drift_candidate_count),
|
||||
detail: t('globalControl.executionQueue.items.versionRadar.detail', {
|
||||
stale: dependencySupplyChainDriftMonitor.rollups.stale_source_snapshot_count,
|
||||
candidates: dependencySupplyChainDriftMonitor.rollups.drift_candidate_count,
|
||||
}),
|
||||
next: t('globalControl.executionQueue.items.versionRadar.next', { blocked: dependencySupplyChainDriftMonitor.rollups.blocked_operation_count }),
|
||||
telegram: t('globalControl.executionQueue.telegram.approvalDigest'),
|
||||
gate: t('globalControl.executionQueue.gates.noPackageUpgrade', { blocked: dependencySupplyChainDriftMonitor.rollups.blocked_operation_count }),
|
||||
tone: dependencySupplyChainDriftMonitor.rollups.blocked_operation_count > 0 ? 'warn' : 'ok',
|
||||
icon: <PackageCheck size={15} />,
|
||||
},
|
||||
{
|
||||
key: 'reportOps',
|
||||
label: t('globalControl.executionQueue.items.reportOps.label'),
|
||||
owner: 'Hermes / Reporter',
|
||||
risk: t('globalControl.executionQueue.risks.lowMedium'),
|
||||
mode: t('globalControl.executionQueue.modes.noSendPreview'),
|
||||
value: `${reportDryRunQueueDrafts}/${reportDryRunArtifacts}`,
|
||||
detail: t('globalControl.executionQueue.items.reportOps.detail', {
|
||||
daily: reportRuntimeReady,
|
||||
drafts: reportDryRunQueueDrafts,
|
||||
charts: visibleReportStatusCharts.length + visibleReportCharts.length,
|
||||
}),
|
||||
next: t('globalControl.executionQueue.items.reportOps.next', { approvals: reportRuntimeApprovals + reportDryRunApprovals + reportFixtureApprovals }),
|
||||
telegram: t('globalControl.executionQueue.telegram.noSendPreview'),
|
||||
gate: t('globalControl.executionQueue.gates.noTelegramLive', { sends: reportDryRunBotCalls + reportFixtureSends }),
|
||||
tone: reportRuntimeApprovals + reportDryRunApprovals + reportFixtureApprovals > 0 ? 'warn' : 'ok',
|
||||
icon: <FileText size={15} />,
|
||||
},
|
||||
{
|
||||
key: 'lowMedium',
|
||||
label: t('globalControl.executionQueue.items.lowMedium.label'),
|
||||
owner: 'OpenClaw / SRE',
|
||||
risk: t('globalControl.executionQueue.risks.lowMedium'),
|
||||
mode: t('globalControl.executionQueue.modes.autoPrepare'),
|
||||
value: String(lowMediumWhitelistCandidates),
|
||||
detail: t('globalControl.executionQueue.items.lowMedium.detail', {
|
||||
low: lowMediumWhitelistLow,
|
||||
medium: lowMediumWhitelistMedium,
|
||||
verifiers: lowMediumWhitelistVerifiers,
|
||||
}),
|
||||
next: t('globalControl.executionQueue.items.lowMedium.next', { blocked: lowMediumWhitelistBlocked }),
|
||||
telegram: t('globalControl.executionQueue.telegram.actionDigest'),
|
||||
gate: t('globalControl.executionQueue.gates.whitelistNoLive', { live: lowMediumWhitelistLiveTotal }),
|
||||
tone: lowMediumWhitelistBlocked > 0 ? 'warn' : 'ok',
|
||||
icon: <ClipboardCheck size={15} />,
|
||||
},
|
||||
{
|
||||
key: 'highRisk',
|
||||
label: t('globalControl.executionQueue.items.highRisk.label'),
|
||||
owner: 'Security / OpenClaw',
|
||||
risk: t('globalControl.executionQueue.risks.highCritical'),
|
||||
mode: t('globalControl.executionQueue.modes.ownerReview'),
|
||||
value: String(highRiskOwnerQueueItems),
|
||||
detail: t('globalControl.executionQueue.items.highRisk.detail', {
|
||||
critical: highRiskOwnerQueueCritical,
|
||||
high: highRiskOwnerQueueHigh,
|
||||
packets: highRiskOwnerQueuePackets,
|
||||
}),
|
||||
next: t('globalControl.executionQueue.items.highRisk.next', { accepted: highRiskOwnerQueueAccepted }),
|
||||
telegram: t('globalControl.executionQueue.telegram.ownerGate'),
|
||||
gate: t('globalControl.executionQueue.gates.highRiskBlocked', { blocked: highRiskOwnerQueueBlocked }),
|
||||
tone: highRiskOwnerQueueItems > 0 ? 'danger' : 'ok',
|
||||
icon: <ShieldAlert size={15} />,
|
||||
},
|
||||
{
|
||||
key: 'shadow',
|
||||
label: t('globalControl.executionQueue.items.shadow.label'),
|
||||
owner: 'Nemotron / Critic',
|
||||
risk: t('globalControl.executionQueue.risks.medium'),
|
||||
mode: t('globalControl.executionQueue.modes.shadowReplay'),
|
||||
value: `${runtimeShadowPassed}/${runtimeShadowCandidates}`,
|
||||
detail: t('globalControl.executionQueue.items.shadow.detail', {
|
||||
replays: runtimeShadowReplays,
|
||||
blocked: runtimeShadowBlocked,
|
||||
review: runtimeShadowNeedsReview,
|
||||
}),
|
||||
next: t('globalControl.executionQueue.items.shadow.next', { checkpoints: runtimeShadowApprovals }),
|
||||
telegram: t('globalControl.executionQueue.telegram.replayDigest'),
|
||||
gate: t('globalControl.executionQueue.gates.noProviderSwitch', { writes: runtimeShadowProductionWrites }),
|
||||
tone: runtimeShadowBlocked + runtimeShadowApprovals > 0 ? 'warn' : 'ok',
|
||||
icon: <RefreshCw size={15} />,
|
||||
},
|
||||
{
|
||||
key: 'learning',
|
||||
label: t('globalControl.executionQueue.items.learning.label'),
|
||||
owner: 'Hermes / OpenClaw',
|
||||
risk: t('globalControl.executionQueue.risks.medium'),
|
||||
mode: t('globalControl.executionQueue.modes.learningDraft'),
|
||||
value: String(matchedPlaybookCandidates + taskResultKmDrafts),
|
||||
detail: t('globalControl.executionQueue.items.learning.detail', {
|
||||
matched: matchedPlaybookApprovalMatched,
|
||||
km: taskResultKmDrafts,
|
||||
gates: matchedPlaybookGates,
|
||||
}),
|
||||
next: t('globalControl.executionQueue.items.learning.next', { writebacks: taskResultWritebacks + matchedPlaybookCandidates }),
|
||||
telegram: t('globalControl.executionQueue.telegram.learningDigest'),
|
||||
gate: t('globalControl.executionQueue.gates.noLearningWrite', { writes: matchedPlaybookLearningWrites + matchedPlaybookTrustWrites + taskResultKmWrites }),
|
||||
tone: matchedPlaybookGates + learningWritebackApprovals > 0 ? 'warn' : 'ok',
|
||||
icon: <BookOpenCheck size={15} />,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="automation-inventory-tab-root" style={{ padding: 20, display: 'flex', flexDirection: 'column', gap: 16, minWidth: 0 }}>
|
||||
@@ -5653,6 +5803,68 @@ export function AutomationInventoryTab() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ padding: 12, border: '0.5px solid #eee1c8', borderRadius: 7, background: '#fffdf7', display: 'flex', flexDirection: 'column', gap: 11, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, minWidth: 0 }}>
|
||||
<SmallLabel>{t('globalControl.executionQueue.title')}</SmallLabel>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#71644b', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
|
||||
{t('globalControl.executionQueue.summary', {
|
||||
rows: globalControlExecutionQueueRows.length,
|
||||
auto: globalControlQueueAutoPrepared,
|
||||
review: globalControlQueueOwnerReview,
|
||||
blocked: globalControlQueueBlocked,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<Chip value={t('globalControl.executionQueue.badge')} muted />
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 10 }} className="automation-inventory-global-control-execution-queue-grid">
|
||||
{globalControlExecutionQueueRows.map(row => {
|
||||
const color = toneColor(row.tone)
|
||||
return (
|
||||
<div key={row.key} style={{ padding: 11, border: `0.5px solid ${color}44`, borderRadius: 7, background: '#fff', display: 'grid', gridTemplateColumns: '30px minmax(0, 1fr)', gap: 9, alignItems: 'start', minWidth: 0 }}>
|
||||
<div style={{
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: 7,
|
||||
border: `0.5px solid ${color}55`,
|
||||
background: `${color}12`,
|
||||
color,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
{row.icon}
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 7, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'flex-start', minWidth: 0 }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 760, color: '#141413', lineHeight: 1.2, overflowWrap: 'anywhere' }}>
|
||||
{row.label}
|
||||
</span>
|
||||
<Chip value={row.value} muted={row.tone === 'ok'} />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
<Chip value={row.owner} />
|
||||
<Chip value={row.risk} muted />
|
||||
<Chip value={row.mode} muted />
|
||||
</div>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#655f52', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
|
||||
{row.detail}
|
||||
</span>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color, fontWeight: 700, lineHeight: 1.45, overflowWrap: 'anywhere' }}>
|
||||
{row.next}
|
||||
</span>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: 6 }} className="automation-inventory-global-control-execution-queue-meta-grid">
|
||||
<Chip value={row.telegram} muted />
|
||||
<Chip value={row.gate} muted />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, minmax(0, 1fr))', gap: 10 }} className="automation-inventory-global-control-domain-grid">
|
||||
{globalControlDomainRows.map(row => {
|
||||
const color = toneColor(row.tone)
|
||||
@@ -18365,6 +18577,8 @@ export function AutomationInventoryTab() {
|
||||
.automation-inventory-global-control-grid,
|
||||
.automation-inventory-global-control-pipeline-grid,
|
||||
.automation-inventory-global-control-runway-grid,
|
||||
.automation-inventory-global-control-execution-queue-grid,
|
||||
.automation-inventory-global-control-execution-queue-meta-grid,
|
||||
.automation-inventory-global-control-domain-grid,
|
||||
.automation-inventory-kpi-grid,
|
||||
.automation-inventory-command-grid,
|
||||
|
||||
Reference in New Issue
Block a user