feat(governance): 新增 post-write verifier package
This commit is contained in:
@@ -42,6 +42,7 @@ import {
|
||||
type AiAgentLiveReadModelGateSnapshot,
|
||||
type AiAgentOwnerApprovedFixtureDryRunSnapshot,
|
||||
type AiAgentOwnerApprovedLearningDryRunSnapshot,
|
||||
type AiAgentPostWriteVerifierPackageSnapshot,
|
||||
type AiAgentProactiveOperationsContractSnapshot,
|
||||
type AiAgentRedisDryRunGateSnapshot,
|
||||
type AiAgentRuntimeWriteGateReviewSnapshot,
|
||||
@@ -331,6 +332,7 @@ export function AutomationInventoryTab() {
|
||||
const [telegramReceiptPackage, setTelegramReceiptPackage] = useState<AiAgentTelegramReceiptApprovalPackageSnapshot | null>(null)
|
||||
const [ownerApprovedLearningDryRun, setOwnerApprovedLearningDryRun] = useState<AiAgentOwnerApprovedLearningDryRunSnapshot | null>(null)
|
||||
const [runtimeWriteGateReview, setRuntimeWriteGateReview] = useState<AiAgentRuntimeWriteGateReviewSnapshot | null>(null)
|
||||
const [postWriteVerifierPackage, setPostWriteVerifierPackage] = useState<AiAgentPostWriteVerifierPackageSnapshot | null>(null)
|
||||
const [ownerDryRunPackage, setOwnerDryRunPackage] = useState<AiAgentOwnerApprovedFixtureDryRunSnapshot | null>(null)
|
||||
const [hostStatefulInventory, setHostStatefulInventory] = useState<AiAgentHostStatefulVersionInventorySnapshot | null>(null)
|
||||
const [serviceHealthGapMatrix, setServiceHealthGapMatrix] = useState<ServiceHealthGapMatrixSnapshot | null>(null)
|
||||
@@ -360,6 +362,7 @@ export function AutomationInventoryTab() {
|
||||
apiClient.getAiAgentTelegramReceiptApprovalPackage(),
|
||||
apiClient.getAiAgentOwnerApprovedLearningDryRun(),
|
||||
apiClient.getAiAgentRuntimeWriteGateReview(),
|
||||
apiClient.getAiAgentPostWriteVerifierPackage(),
|
||||
apiClient.getAiAgentOwnerApprovedFixtureDryRun(),
|
||||
apiClient.getAiAgentHostStatefulVersionInventory(),
|
||||
apiClient.getServiceHealthGapMatrix(),
|
||||
@@ -388,6 +391,7 @@ export function AutomationInventoryTab() {
|
||||
telegramReceiptPackageResult,
|
||||
ownerApprovedLearningDryRunResult,
|
||||
runtimeWriteGateReviewResult,
|
||||
postWriteVerifierPackageResult,
|
||||
ownerDryRunPackageResult,
|
||||
hostStatefulInventoryResult,
|
||||
serviceHealthGapMatrixResult,
|
||||
@@ -413,6 +417,7 @@ export function AutomationInventoryTab() {
|
||||
setTelegramReceiptPackage(telegramReceiptPackageResult.status === 'fulfilled' ? telegramReceiptPackageResult.value : null)
|
||||
setOwnerApprovedLearningDryRun(ownerApprovedLearningDryRunResult.status === 'fulfilled' ? ownerApprovedLearningDryRunResult.value : null)
|
||||
setRuntimeWriteGateReview(runtimeWriteGateReviewResult.status === 'fulfilled' ? runtimeWriteGateReviewResult.value : null)
|
||||
setPostWriteVerifierPackage(postWriteVerifierPackageResult.status === 'fulfilled' ? postWriteVerifierPackageResult.value : null)
|
||||
setOwnerDryRunPackage(ownerDryRunPackageResult.status === 'fulfilled' ? ownerDryRunPackageResult.value : null)
|
||||
setHostStatefulInventory(hostStatefulInventoryResult.status === 'fulfilled' ? hostStatefulInventoryResult.value : null)
|
||||
setServiceHealthGapMatrix(serviceHealthGapMatrixResult.status === 'fulfilled' ? serviceHealthGapMatrixResult.value : null)
|
||||
@@ -436,6 +441,7 @@ export function AutomationInventoryTab() {
|
||||
telegramReceiptPackageResult,
|
||||
ownerApprovedLearningDryRunResult,
|
||||
runtimeWriteGateReviewResult,
|
||||
postWriteVerifierPackageResult,
|
||||
ownerDryRunPackageResult,
|
||||
hostStatefulInventoryResult,
|
||||
serviceHealthGapMatrixResult,
|
||||
@@ -750,6 +756,42 @@ export function AutomationInventoryTab() {
|
||||
})
|
||||
}, [runtimeWriteGateReview])
|
||||
|
||||
const visiblePostWriteVerifierTargets = useMemo(() => {
|
||||
if (!postWriteVerifierPackage) return []
|
||||
const priority = { approval_required: 0, blocked_by_runtime_gate: 1, contract_ready: 2 } as Record<string, number>
|
||||
return [...postWriteVerifierPackage.verification_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)
|
||||
})
|
||||
}, [postWriteVerifierPackage])
|
||||
|
||||
const visiblePostWriteVerifierLanes = useMemo(() => {
|
||||
if (!postWriteVerifierPackage) return []
|
||||
const priority = { approval_required: 0, blocked_by_runtime_gate: 1, contract_ready: 2 } as Record<string, number>
|
||||
return [...postWriteVerifierPackage.failure_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)
|
||||
})
|
||||
}, [postWriteVerifierPackage])
|
||||
|
||||
const visiblePostWriteVerifierActions = useMemo(() => {
|
||||
if (!postWriteVerifierPackage) return []
|
||||
const priority = { approval_required: 0, ready_for_owner: 1, blocked_by_runtime_gate: 2 } as Record<string, number>
|
||||
return [...postWriteVerifierPackage.operator_actions]
|
||||
.sort((a, b) => {
|
||||
const left = priority[a.status] ?? 3
|
||||
const right = priority[b.status] ?? 3
|
||||
if (left !== right) return left - right
|
||||
return a.action_id.localeCompare(b.action_id)
|
||||
})
|
||||
}, [postWriteVerifierPackage])
|
||||
|
||||
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>
|
||||
@@ -929,7 +971,7 @@ export function AutomationInventoryTab() {
|
||||
)
|
||||
}
|
||||
|
||||
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
|
||||
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !postWriteVerifierPackage || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
|
||||
return (
|
||||
<div style={{ padding: 20 }}>
|
||||
<GlassCard variant="subtle" padding="lg">
|
||||
@@ -1054,6 +1096,13 @@ export function AutomationInventoryTab() {
|
||||
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 postWriteVerifierOverall = postWriteVerifierPackage.program_status.overall_completion_percent
|
||||
const postWriteVerifierTargets = postWriteVerifierPackage.rollups.verification_target_count
|
||||
const postWriteVerifierFailureLanes = postWriteVerifierPackage.rollups.failure_lane_count
|
||||
const postWriteVerifierActions = postWriteVerifierPackage.rollups.operator_action_count
|
||||
const postWriteVerifierApprovals = postWriteVerifierPackage.rollups.approval_required_action_ids.length
|
||||
const postWriteVerifierBlockedActions = postWriteVerifierPackage.rollups.blocked_runtime_action_count
|
||||
const postWriteVerifierLiveTotal = postWriteVerifierPackage.rollups.live_verifier_execution_count
|
||||
const ownerDryRunOverall = ownerDryRunPackage.program_status.overall_completion_percent
|
||||
const ownerDryRunFixtures = ownerDryRunPackage.rollups.fixture_set_count
|
||||
const ownerDryRunGates = ownerDryRunPackage.rollups.dry_run_gate_count
|
||||
@@ -1745,6 +1794,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 }}>
|
||||
<PackageCheck size={14} style={{ color: '#d97757' }} />
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{t('postWriteVerifierPackage.title')}
|
||||
</span>
|
||||
</div>
|
||||
<Chip
|
||||
value={t('postWriteVerifierPackage.source', {
|
||||
generated: formatDateTime(postWriteVerifierPackage.generated_at),
|
||||
current: postWriteVerifierPackage.program_status.current_task_id,
|
||||
next: postWriteVerifierPackage.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('postWriteVerifierPackage.metrics.overall')} value={`${postWriteVerifierOverall}%`} tone="warn" icon={<Gauge size={16} />} />
|
||||
<MetricCard label={t('postWriteVerifierPackage.metrics.targets')} value={postWriteVerifierTargets} tone="ok" icon={<Target size={16} />} />
|
||||
<MetricCard label={t('postWriteVerifierPackage.metrics.lanes')} value={postWriteVerifierFailureLanes} tone="warn" icon={<GitBranch size={16} />} />
|
||||
<MetricCard label={t('postWriteVerifierPackage.metrics.actions')} value={postWriteVerifierActions} tone="ok" icon={<FileText size={16} />} />
|
||||
<MetricCard label={t('postWriteVerifierPackage.metrics.approval')} value={postWriteVerifierApprovals} tone={postWriteVerifierApprovals > 0 ? 'danger' : 'ok'} icon={<Lock size={16} />} />
|
||||
<MetricCard label={t('postWriteVerifierPackage.metrics.live')} value={postWriteVerifierLiveTotal} tone={postWriteVerifierLiveTotal === 0 ? 'warn' : 'ok'} icon={<Database size={16} />} />
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 0.9fr) minmax(0, 1.1fr)', 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('postWriteVerifierPackage.packageTitle')}</SmallLabel>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
|
||||
{postWriteVerifierPackage.verifier_package.operator_meaning}
|
||||
</span>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
<Chip value={t('postWriteVerifierPackage.labels.requiredInputs', { count: postWriteVerifierPackage.rollups.required_input_count })} />
|
||||
<Chip value={t('postWriteVerifierPackage.labels.forbiddenInputs', { count: postWriteVerifierPackage.rollups.forbidden_input_count })} muted />
|
||||
<Chip value={redisDryRunValueLabel('agents', postWriteVerifierPackage.verifier_package.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('postWriteVerifierPackage.truthTitle')}</SmallLabel>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
|
||||
{postWriteVerifierPackage.verifier_truth.truth_note}
|
||||
</span>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
<Chip value={t('postWriteVerifierPackage.flags.implemented', { value: String(postWriteVerifierPackage.verifier_truth.post_write_verifier_implemented) })} />
|
||||
<Chip value={t('postWriteVerifierPackage.flags.canonical', { value: String(postWriteVerifierPackage.verifier_truth.canonical_readback_allowed) })} muted />
|
||||
<Chip value={t('postWriteVerifierPackage.flags.rollback', { value: postWriteVerifierPackage.verifier_truth.rollback_work_item_created_count })} muted />
|
||||
<Chip value={t('postWriteVerifierPackage.flags.telegram', { value: postWriteVerifierPackage.verifier_truth.telegram_failure_receipt_sent_count })} 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('postWriteVerifierPackage.failureTitle')}</SmallLabel>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
|
||||
{postWriteVerifierPackage.verifier_package.failure_policy}
|
||||
</span>
|
||||
<Chip value={t('postWriteVerifierPackage.metrics.blocked', { count: postWriteVerifierBlockedActions })} muted />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
|
||||
{visiblePostWriteVerifierTargets.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">
|
||||
{[...visiblePostWriteVerifierLanes, ...visiblePostWriteVerifierActions].map(item => (
|
||||
<div key={'lane_id' in item ? item.lane_id : item.action_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' }}>
|
||||
{item.display_name}
|
||||
</span>
|
||||
<Chip value={redisDryRunValueLabel('statuses', item.status)} muted={item.status !== 'approval_required'} />
|
||||
</div>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
|
||||
{'required_evidence' in item ? item.required_evidence : item.operator_instruction}
|
||||
</span>
|
||||
<Chip value={item.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 }}>
|
||||
|
||||
@@ -307,6 +307,11 @@ export const apiClient = {
|
||||
return handleResponse<AiAgentRuntimeWriteGateReviewSnapshot>(res)
|
||||
},
|
||||
|
||||
async getAiAgentPostWriteVerifierPackage() {
|
||||
const res = await fetch(`${API_BASE_URL}/agents/agent-post-write-verifier-package`)
|
||||
return handleResponse<AiAgentPostWriteVerifierPackageSnapshot>(res)
|
||||
},
|
||||
|
||||
async getAiAgentOwnerApprovedFixtureDryRun() {
|
||||
const res = await fetch(`${API_BASE_URL}/agents/agent-owner-approved-fixture-dry-run`)
|
||||
return handleResponse<AiAgentOwnerApprovedFixtureDryRunSnapshot>(res)
|
||||
@@ -1675,6 +1680,88 @@ export interface AiAgentRuntimeWriteGateReviewSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
export interface AiAgentPostWriteVerifierPackageSnapshot {
|
||||
schema_version: 'ai_agent_post_write_verifier_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: 'post_write_verifier_package_only_no_runtime_write'
|
||||
status_note: string
|
||||
}
|
||||
source_refs: string[]
|
||||
verifier_truth: {
|
||||
runtime_write_allowed: false
|
||||
post_write_verifier_implemented: false
|
||||
post_write_verifier_executed_count: number
|
||||
rollback_work_item_created_count: number
|
||||
telegram_failure_receipt_sent_count: number
|
||||
canonical_readback_allowed: false
|
||||
truth_note: string
|
||||
}
|
||||
verifier_package: {
|
||||
package_id: string
|
||||
display_name: string
|
||||
owner_agent: 'openclaw' | 'hermes' | 'nemotron'
|
||||
status: string
|
||||
operator_meaning: string
|
||||
required_inputs: string[]
|
||||
forbidden_inputs: string[]
|
||||
success_policy: string
|
||||
failure_policy: string
|
||||
}
|
||||
verification_targets: Array<{
|
||||
target_id: string
|
||||
display_name: string
|
||||
target_surface: string
|
||||
status: string
|
||||
owner_agent: 'openclaw' | 'hermes' | 'nemotron'
|
||||
required_readback: string
|
||||
blocked_runtime_action: string
|
||||
operator_instruction: string
|
||||
}>
|
||||
failure_lanes: Array<{
|
||||
lane_id: string
|
||||
display_name: string
|
||||
status: string
|
||||
required_evidence: string
|
||||
blocked_runtime_action: string
|
||||
operator_instruction: string
|
||||
}>
|
||||
operator_actions: Array<{
|
||||
action_id: string
|
||||
display_name: string
|
||||
action_type: 'review' | 'collect_evidence' | 'approve_implementation' | 'reject_or_rework'
|
||||
status: string
|
||||
owner_agent: 'openclaw' | 'hermes' | 'nemotron'
|
||||
operator_instruction: string
|
||||
blocked_runtime_action: 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: {
|
||||
verification_target_count: number
|
||||
failure_lane_count: number
|
||||
operator_action_count: number
|
||||
approval_required_action_ids: string[]
|
||||
blocked_runtime_action_count: number
|
||||
required_input_count: number
|
||||
forbidden_input_count: number
|
||||
live_verifier_execution_count: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface AiAgentOwnerApprovedFixtureDryRunSnapshot {
|
||||
schema_version: 'ai_agent_owner_approved_fixture_dry_run_v1'
|
||||
generated_at: string
|
||||
|
||||
Reference in New Issue
Block a user