feat(governance): 新增 runtime write gate review
This commit is contained in:
@@ -44,6 +44,7 @@ import {
|
||||
type AiAgentOwnerApprovedLearningDryRunSnapshot,
|
||||
type AiAgentProactiveOperationsContractSnapshot,
|
||||
type AiAgentRedisDryRunGateSnapshot,
|
||||
type AiAgentRuntimeWriteGateReviewSnapshot,
|
||||
type AiAgentTelegramReceiptApprovalPackageSnapshot,
|
||||
type AiProviderRouteMatrixSnapshot,
|
||||
type AiAgentAutomationBacklogSnapshot,
|
||||
@@ -329,6 +330,7 @@ export function AutomationInventoryTab() {
|
||||
const [learningWritebackPackage, setLearningWritebackPackage] = useState<AiAgentLearningWritebackApprovalPackageSnapshot | null>(null)
|
||||
const [telegramReceiptPackage, setTelegramReceiptPackage] = useState<AiAgentTelegramReceiptApprovalPackageSnapshot | null>(null)
|
||||
const [ownerApprovedLearningDryRun, setOwnerApprovedLearningDryRun] = useState<AiAgentOwnerApprovedLearningDryRunSnapshot | null>(null)
|
||||
const [runtimeWriteGateReview, setRuntimeWriteGateReview] = useState<AiAgentRuntimeWriteGateReviewSnapshot | null>(null)
|
||||
const [ownerDryRunPackage, setOwnerDryRunPackage] = useState<AiAgentOwnerApprovedFixtureDryRunSnapshot | null>(null)
|
||||
const [hostStatefulInventory, setHostStatefulInventory] = useState<AiAgentHostStatefulVersionInventorySnapshot | null>(null)
|
||||
const [serviceHealthGapMatrix, setServiceHealthGapMatrix] = useState<ServiceHealthGapMatrixSnapshot | null>(null)
|
||||
@@ -357,6 +359,7 @@ export function AutomationInventoryTab() {
|
||||
apiClient.getAiAgentLearningWritebackApprovalPackage(),
|
||||
apiClient.getAiAgentTelegramReceiptApprovalPackage(),
|
||||
apiClient.getAiAgentOwnerApprovedLearningDryRun(),
|
||||
apiClient.getAiAgentRuntimeWriteGateReview(),
|
||||
apiClient.getAiAgentOwnerApprovedFixtureDryRun(),
|
||||
apiClient.getAiAgentHostStatefulVersionInventory(),
|
||||
apiClient.getServiceHealthGapMatrix(),
|
||||
@@ -384,6 +387,7 @@ export function AutomationInventoryTab() {
|
||||
learningWritebackPackageResult,
|
||||
telegramReceiptPackageResult,
|
||||
ownerApprovedLearningDryRunResult,
|
||||
runtimeWriteGateReviewResult,
|
||||
ownerDryRunPackageResult,
|
||||
hostStatefulInventoryResult,
|
||||
serviceHealthGapMatrixResult,
|
||||
@@ -408,6 +412,7 @@ export function AutomationInventoryTab() {
|
||||
setLearningWritebackPackage(learningWritebackPackageResult.status === 'fulfilled' ? learningWritebackPackageResult.value : null)
|
||||
setTelegramReceiptPackage(telegramReceiptPackageResult.status === 'fulfilled' ? telegramReceiptPackageResult.value : null)
|
||||
setOwnerApprovedLearningDryRun(ownerApprovedLearningDryRunResult.status === 'fulfilled' ? ownerApprovedLearningDryRunResult.value : null)
|
||||
setRuntimeWriteGateReview(runtimeWriteGateReviewResult.status === 'fulfilled' ? runtimeWriteGateReviewResult.value : null)
|
||||
setOwnerDryRunPackage(ownerDryRunPackageResult.status === 'fulfilled' ? ownerDryRunPackageResult.value : null)
|
||||
setHostStatefulInventory(hostStatefulInventoryResult.status === 'fulfilled' ? hostStatefulInventoryResult.value : null)
|
||||
setServiceHealthGapMatrix(serviceHealthGapMatrixResult.status === 'fulfilled' ? serviceHealthGapMatrixResult.value : null)
|
||||
@@ -430,6 +435,7 @@ export function AutomationInventoryTab() {
|
||||
learningWritebackPackageResult,
|
||||
telegramReceiptPackageResult,
|
||||
ownerApprovedLearningDryRunResult,
|
||||
runtimeWriteGateReviewResult,
|
||||
ownerDryRunPackageResult,
|
||||
hostStatefulInventoryResult,
|
||||
serviceHealthGapMatrixResult,
|
||||
@@ -720,6 +726,30 @@ export function AutomationInventoryTab() {
|
||||
})
|
||||
}, [ownerApprovedLearningDryRun])
|
||||
|
||||
const visibleRuntimeWriteTargets = useMemo(() => {
|
||||
if (!runtimeWriteGateReview) return []
|
||||
const priority = { approval_required: 0, blocked_by_runtime_gate: 1, contract_ready: 2 } as Record<string, number>
|
||||
return [...runtimeWriteGateReview.write_targets]
|
||||
.sort((a, b) => {
|
||||
const left = priority[a.status] ?? 3
|
||||
const right = priority[b.status] ?? 3
|
||||
if (left !== right) return left - right
|
||||
return a.target_id.localeCompare(b.target_id)
|
||||
})
|
||||
}, [runtimeWriteGateReview])
|
||||
|
||||
const visibleRuntimeWriteGates = useMemo(() => {
|
||||
if (!runtimeWriteGateReview) return []
|
||||
const priority = { approval_required: 0, blocked_by_runtime_gate: 1, contract_ready: 2 } as Record<string, number>
|
||||
return [...runtimeWriteGateReview.approval_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)
|
||||
})
|
||||
}, [runtimeWriteGateReview])
|
||||
|
||||
const visibleOwnerDryRunGates = useMemo(() => {
|
||||
if (!ownerDryRunPackage) return []
|
||||
const priority = { approval_required: 0, approved_for_fixture_only: 1, fixture_only: 2, ready: 3 } as Record<string, number>
|
||||
@@ -899,7 +929,7 @@ export function AutomationInventoryTab() {
|
||||
)
|
||||
}
|
||||
|
||||
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
|
||||
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
|
||||
return (
|
||||
<div style={{ padding: 20 }}>
|
||||
<GlassCard variant="subtle" padding="lg">
|
||||
@@ -1018,6 +1048,12 @@ export function AutomationInventoryTab() {
|
||||
const ownerApprovedLearningBlockedWrites = ownerApprovedLearningDryRun.rollups.blocked_write_action_count
|
||||
const ownerApprovedLearningPreviewOutputs = ownerApprovedLearningDryRun.rollups.preview_output_count
|
||||
const ownerApprovedLearningGenerated = ownerApprovedLearningDryRun.rollups.dry_run_preview_generated_count
|
||||
const runtimeWriteGateOverall = runtimeWriteGateReview.program_status.overall_completion_percent
|
||||
const runtimeWriteTargets = runtimeWriteGateReview.rollups.write_target_count
|
||||
const runtimeWriteGates = runtimeWriteGateReview.rollups.approval_gate_count
|
||||
const runtimeWriteApprovals = runtimeWriteGateReview.rollups.approval_required_gate_ids.length
|
||||
const runtimeWriteBlockedActions = runtimeWriteGateReview.rollups.blocked_runtime_action_count
|
||||
const runtimeWriteLiveTotal = runtimeWriteGateReview.rollups.live_write_count_total
|
||||
const ownerDryRunOverall = ownerDryRunPackage.program_status.overall_completion_percent
|
||||
const ownerDryRunFixtures = ownerDryRunPackage.rollups.fixture_set_count
|
||||
const ownerDryRunGates = ownerDryRunPackage.rollups.dry_run_gate_count
|
||||
@@ -1593,6 +1629,114 @@ 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 }}>
|
||||
<Lock size={14} style={{ color: '#d97757' }} />
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{t('runtimeWriteGateReview.title')}
|
||||
</span>
|
||||
</div>
|
||||
<Chip
|
||||
value={t('runtimeWriteGateReview.source', {
|
||||
generated: formatDateTime(runtimeWriteGateReview.generated_at),
|
||||
current: runtimeWriteGateReview.program_status.current_task_id,
|
||||
next: runtimeWriteGateReview.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('runtimeWriteGateReview.metrics.overall')} value={`${runtimeWriteGateOverall}%`} tone="warn" icon={<Gauge size={16} />} />
|
||||
<MetricCard label={t('runtimeWriteGateReview.metrics.targets')} value={runtimeWriteTargets} tone="ok" icon={<Target size={16} />} />
|
||||
<MetricCard label={t('runtimeWriteGateReview.metrics.gates')} value={runtimeWriteGates} tone="ok" icon={<ShieldCheck size={16} />} />
|
||||
<MetricCard label={t('runtimeWriteGateReview.metrics.approval')} value={runtimeWriteApprovals} tone={runtimeWriteApprovals > 0 ? 'danger' : 'ok'} icon={<Lock size={16} />} />
|
||||
<MetricCard label={t('runtimeWriteGateReview.metrics.blocked')} value={runtimeWriteBlockedActions} tone={runtimeWriteBlockedActions > 0 ? 'danger' : 'ok'} icon={<ShieldAlert size={16} />} />
|
||||
<MetricCard label={t('runtimeWriteGateReview.metrics.live')} value={runtimeWriteLiveTotal} tone={runtimeWriteLiveTotal === 0 ? 'warn' : 'ok'} icon={<Database 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('runtimeWriteGateReview.reviewTitle')}</SmallLabel>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
|
||||
{runtimeWriteGateReview.write_gate_review.operator_meaning}
|
||||
</span>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
<Chip value={t('runtimeWriteGateReview.labels.requiredFields', { count: runtimeWriteGateReview.rollups.required_field_count })} />
|
||||
<Chip value={t('runtimeWriteGateReview.labels.forbiddenFields', { count: runtimeWriteGateReview.rollups.forbidden_field_count })} muted />
|
||||
<Chip value={redisDryRunValueLabel('agents', runtimeWriteGateReview.write_gate_review.owner_agent)} 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('runtimeWriteGateReview.truthTitle')}</SmallLabel>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
|
||||
{runtimeWriteGateReview.runtime_write_truth.truth_note}
|
||||
</span>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
<Chip value={t('runtimeWriteGateReview.flags.dualApproval', { value: runtimeWriteGateReview.runtime_write_truth.dual_approval_received_count })} />
|
||||
<Chip value={t('runtimeWriteGateReview.flags.hash', { value: runtimeWriteGateReview.runtime_write_truth.dry_run_hash_verified_count })} muted />
|
||||
<Chip value={t('runtimeWriteGateReview.flags.verifier', { value: runtimeWriteGateReview.runtime_write_truth.post_write_verifier_pass_count })} muted />
|
||||
<Chip value={t('runtimeWriteGateReview.flags.runtime', { value: String(runtimeWriteGateReview.runtime_write_truth.runtime_write_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('runtimeWriteGateReview.verificationTitle')}</SmallLabel>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
|
||||
{runtimeWriteGateReview.post_write_verification.verification_steps.slice(0, 5).map(step => (
|
||||
<span key={step} style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
|
||||
{step}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
|
||||
{visibleRuntimeWriteTargets.map(target => (
|
||||
<div key={target.target_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' }}>
|
||||
{target.target_id}
|
||||
</span>
|
||||
<Chip value={redisDryRunValueLabel('statuses', target.status)} muted={target.status === 'contract_ready'} />
|
||||
</div>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', lineHeight: 1.35 }}>
|
||||
{target.display_name}
|
||||
</span>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
|
||||
{target.operator_instruction}
|
||||
</span>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
<Chip value={redisDryRunValueLabel('agents', target.owner_agent)} muted />
|
||||
<Chip value={target.blocked_runtime_action} muted />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(190px, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
|
||||
{visibleRuntimeWriteGates.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: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', overflowWrap: 'anywhere' }}>
|
||||
{gate.display_name}
|
||||
</span>
|
||||
<Chip value={redisDryRunValueLabel('statuses', gate.status)} muted={gate.status === 'contract_ready'} />
|
||||
</div>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
|
||||
{gate.required_evidence}
|
||||
</span>
|
||||
<Chip value={gate.blocked_runtime_action} muted />
|
||||
</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 }}>
|
||||
|
||||
@@ -302,6 +302,11 @@ export const apiClient = {
|
||||
return handleResponse<AiAgentOwnerApprovedLearningDryRunSnapshot>(res)
|
||||
},
|
||||
|
||||
async getAiAgentRuntimeWriteGateReview() {
|
||||
const res = await fetch(`${API_BASE_URL}/agents/agent-runtime-write-gate-review`)
|
||||
return handleResponse<AiAgentRuntimeWriteGateReviewSnapshot>(res)
|
||||
},
|
||||
|
||||
async getAiAgentOwnerApprovedFixtureDryRun() {
|
||||
const res = await fetch(`${API_BASE_URL}/agents/agent-owner-approved-fixture-dry-run`)
|
||||
return handleResponse<AiAgentOwnerApprovedFixtureDryRunSnapshot>(res)
|
||||
@@ -1596,6 +1601,88 @@ export interface AiAgentOwnerApprovedLearningDryRunSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
export interface AiAgentRuntimeWriteGateReviewSnapshot {
|
||||
schema_version: 'ai_agent_runtime_write_gate_review_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: 'write_gate_review_only_no_runtime_write'
|
||||
status_note: string
|
||||
}
|
||||
source_refs: string[]
|
||||
runtime_write_truth: {
|
||||
runtime_write_allowed: false
|
||||
dual_approval_required: true
|
||||
dual_approval_received_count: number
|
||||
dry_run_hash_required: true
|
||||
dry_run_hash_verified_count: number
|
||||
post_write_verifier_required: true
|
||||
post_write_verifier_pass_count: number
|
||||
km_write_allowed: false
|
||||
playbook_trust_write_allowed: false
|
||||
timeline_learning_write_allowed: false
|
||||
agent_replay_score_write_allowed: false
|
||||
telegram_send_allowed: false
|
||||
truth_note: string
|
||||
}
|
||||
write_gate_review: {
|
||||
review_id: string
|
||||
display_name: string
|
||||
owner_agent: 'openclaw' | 'hermes' | 'nemotron'
|
||||
status: string
|
||||
operator_meaning: string
|
||||
required_fields: string[]
|
||||
forbidden_fields: string[]
|
||||
}
|
||||
write_targets: Array<{
|
||||
target_id: string
|
||||
display_name: string
|
||||
target_surface: string
|
||||
status: string
|
||||
owner_agent: 'openclaw' | 'hermes' | 'nemotron'
|
||||
required_approval: string
|
||||
blocked_runtime_action: string
|
||||
operator_instruction: string
|
||||
}>
|
||||
approval_gates: Array<{
|
||||
gate_id: string
|
||||
display_name: string
|
||||
status: string
|
||||
required_evidence: string
|
||||
blocked_runtime_action: string
|
||||
operator_instruction: string
|
||||
}>
|
||||
post_write_verification: {
|
||||
verification_required: true
|
||||
rollback_required: true
|
||||
verification_steps: string[]
|
||||
failure_escalation: 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: {
|
||||
write_target_count: number
|
||||
approval_gate_count: number
|
||||
approval_required_gate_ids: string[]
|
||||
blocked_runtime_action_count: number
|
||||
required_field_count: number
|
||||
forbidden_field_count: number
|
||||
live_write_count_total: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface AiAgentOwnerApprovedFixtureDryRunSnapshot {
|
||||
schema_version: 'ai_agent_owner_approved_fixture_dry_run_v1'
|
||||
generated_at: string
|
||||
|
||||
Reference in New Issue
Block a user