feat(governance): 新增 runtime write gate review
Some checks failed
CD Pipeline / tests (push) Successful in 1m30s
Code Review / ai-code-review (push) Successful in 15s
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-12 00:39:10 +08:00
parent ac39e4fb2f
commit 7a7daa333e
21 changed files with 1017 additions and 56 deletions

View File

@@ -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 }}>

View File

@@ -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