feat(governance): 新增報表 runtime 啟動前閘門
Some checks failed
CD Pipeline / tests (push) Successful in 1m35s
Code Review / ai-code-review (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Successful in 6m19s
CD Pipeline / post-deploy-checks (push) Successful in 1m30s

This commit is contained in:
Your Name
2026-06-12 11:34:15 +08:00
parent 1da56ac56c
commit b17a28c293
17 changed files with 1602 additions and 15 deletions

View File

@@ -46,6 +46,7 @@ import {
type AiAgentProactiveOperationsContractSnapshot,
type AiAgentRedisDryRunGateSnapshot,
type AiAgentReportAutomationReviewSnapshot,
type AiAgentReportRuntimeReadinessSnapshot,
type AiAgentReportTruthActionabilityReviewSnapshot,
type AiAgentRuntimeVerifierEvidenceReviewSnapshot,
type AiAgentRuntimeWriteGateReviewSnapshot,
@@ -338,6 +339,7 @@ export function AutomationInventoryTab() {
const [postWriteVerifierPackage, setPostWriteVerifierPackage] = useState<AiAgentPostWriteVerifierPackageSnapshot | null>(null)
const [runtimeVerifierEvidenceReview, setRuntimeVerifierEvidenceReview] = useState<AiAgentRuntimeVerifierEvidenceReviewSnapshot | null>(null)
const [reportAutomationReview, setReportAutomationReview] = useState<AiAgentReportAutomationReviewSnapshot | null>(null)
const [reportRuntimeReadiness, setReportRuntimeReadiness] = useState<AiAgentReportRuntimeReadinessSnapshot | null>(null)
const [reportTruthActionabilityReview, setReportTruthActionabilityReview] = useState<AiAgentReportTruthActionabilityReviewSnapshot | null>(null)
const [ownerDryRunPackage, setOwnerDryRunPackage] = useState<AiAgentOwnerApprovedFixtureDryRunSnapshot | null>(null)
const [hostStatefulInventory, setHostStatefulInventory] = useState<AiAgentHostStatefulVersionInventorySnapshot | null>(null)
@@ -371,6 +373,7 @@ export function AutomationInventoryTab() {
apiClient.getAiAgentPostWriteVerifierPackage(),
apiClient.getAiAgentRuntimeVerifierEvidenceReview(),
apiClient.getAiAgentReportAutomationReview(),
apiClient.getAiAgentReportRuntimeReadiness(),
apiClient.getAiAgentReportTruthActionabilityReview(),
apiClient.getAiAgentOwnerApprovedFixtureDryRun(),
apiClient.getAiAgentHostStatefulVersionInventory(),
@@ -403,6 +406,7 @@ export function AutomationInventoryTab() {
postWriteVerifierPackageResult,
runtimeVerifierEvidenceReviewResult,
reportAutomationReviewResult,
reportRuntimeReadinessResult,
reportTruthActionabilityReviewResult,
ownerDryRunPackageResult,
hostStatefulInventoryResult,
@@ -432,6 +436,7 @@ export function AutomationInventoryTab() {
setPostWriteVerifierPackage(postWriteVerifierPackageResult.status === 'fulfilled' ? postWriteVerifierPackageResult.value : null)
setRuntimeVerifierEvidenceReview(runtimeVerifierEvidenceReviewResult.status === 'fulfilled' ? runtimeVerifierEvidenceReviewResult.value : null)
setReportAutomationReview(reportAutomationReviewResult.status === 'fulfilled' ? reportAutomationReviewResult.value : null)
setReportRuntimeReadiness(reportRuntimeReadinessResult.status === 'fulfilled' ? reportRuntimeReadinessResult.value : null)
setReportTruthActionabilityReview(reportTruthActionabilityReviewResult.status === 'fulfilled' ? reportTruthActionabilityReviewResult.value : null)
setOwnerDryRunPackage(ownerDryRunPackageResult.status === 'fulfilled' ? ownerDryRunPackageResult.value : null)
setHostStatefulInventory(hostStatefulInventoryResult.status === 'fulfilled' ? hostStatefulInventoryResult.value : null)
@@ -459,6 +464,7 @@ export function AutomationInventoryTab() {
postWriteVerifierPackageResult,
runtimeVerifierEvidenceReviewResult,
reportAutomationReviewResult,
reportRuntimeReadinessResult,
reportTruthActionabilityReviewResult,
ownerDryRunPackageResult,
hostStatefulInventoryResult,
@@ -864,6 +870,36 @@ export function AutomationInventoryTab() {
.slice(0, 5)
}, [reportAutomationReview])
const visibleReportRuntimeLanes = useMemo(() => {
if (!reportRuntimeReadiness) return []
const priority = { blocked_by_runtime_gate: 0, ready_for_owner_review: 1 } as Record<string, number>
return [...reportRuntimeReadiness.runtime_lanes]
.sort((a, b) => {
const left = priority[a.contract_status] ?? 2
const right = priority[b.contract_status] ?? 2
if (left !== right) return left - right
return a.lane_id.localeCompare(b.lane_id)
})
.slice(0, 7)
}, [reportRuntimeReadiness])
const visibleReportRuntimeDecisions = useMemo(() => {
if (!reportRuntimeReadiness) return []
const statusPriority = { approval_required: 0, blocked_by_runtime_gate: 1, ready_for_review: 2 } as Record<string, number>
const riskPriority = { critical: 0, high: 1, medium: 2, low: 3 } as Record<string, number>
return [...reportRuntimeReadiness.operator_decisions]
.sort((a, b) => {
const left = statusPriority[a.status] ?? 3
const right = statusPriority[b.status] ?? 3
if (left !== right) return left - right
const leftRisk = riskPriority[a.risk_tier] ?? 4
const rightRisk = riskPriority[b.risk_tier] ?? 4
if (leftRisk !== rightRisk) return leftRisk - rightRisk
return a.decision_id.localeCompare(b.decision_id)
})
.slice(0, 7)
}, [reportRuntimeReadiness])
const visibleReportTruthFindings = useMemo(() => {
if (!reportTruthActionabilityReview) return []
const priority = { critical: 0, high: 1, medium: 2, low: 3 } as Record<string, number>
@@ -1083,7 +1119,7 @@ export function AutomationInventoryTab() {
)
}
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !postWriteVerifierPackage || !runtimeVerifierEvidenceReview || !reportAutomationReview || !reportTruthActionabilityReview || !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 || !runtimeVerifierEvidenceReview || !reportAutomationReview || !reportRuntimeReadiness || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
return (
<div style={{ padding: 20 }}>
<GlassCard variant="subtle" padding="lg">
@@ -1232,6 +1268,16 @@ export function AutomationInventoryTab() {
const reportAutoEnabled = reportAutomationReview.rollups.current_auto_execution_enabled_count
const reportLiveDelivery = reportAutomationReview.rollups.live_report_delivery_count
const reportLiveOptimization = reportAutomationReview.rollups.live_auto_optimization_count
const reportRuntimeOverall = reportRuntimeReadiness.program_status.overall_completion_percent
const reportRuntimeLanes = reportRuntimeReadiness.rollups.runtime_lane_count
const reportRuntimeReady = reportRuntimeReadiness.rollups.ready_contract_count
const reportRuntimeBlocked = reportRuntimeReadiness.rollups.blocked_contract_count
const reportRuntimeApprovals = reportRuntimeReadiness.rollups.approval_required_decision_ids.length
const reportRuntimeEnabled = reportRuntimeReadiness.rollups.current_enabled_count
const reportRuntimeLiveDelivery = reportRuntimeReadiness.rollups.live_report_delivery_count
const reportRuntimeQueueWrites = reportRuntimeReadiness.rollups.telegram_gateway_queue_write_count
const reportRuntimeAiRuns = reportRuntimeReadiness.rollups.live_ai_analysis_count
const reportRuntimeMediumLowRuns = reportRuntimeReadiness.rollups.live_medium_low_auto_execution_count
const reportTruthOverall = reportTruthActionabilityReview.program_status.overall_completion_percent
const reportTruthFindings = reportTruthActionabilityReview.rollups.zero_signal_finding_count
const reportTruthCritical = reportTruthActionabilityReview.rollups.critical_finding_count
@@ -1997,6 +2043,132 @@ export function AutomationInventoryTab() {
</div>
</div>
<div style={{ padding: 12, border: '0.5px solid #b9d0dd', borderRadius: 7, background: '#f8fcff', 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 }}>
<Route size={14} style={{ color: '#0284c7' }} />
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
{t('reportRuntimeReadiness.title')}
</span>
</div>
<Chip
value={t('reportRuntimeReadiness.source', {
generated: formatDateTime(reportRuntimeReadiness.generated_at),
current: reportRuntimeReadiness.program_status.current_task_id,
next: reportRuntimeReadiness.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('reportRuntimeReadiness.metrics.overall')} value={`${reportRuntimeOverall}%`} tone="warn" icon={<Gauge size={16} />} />
<MetricCard label={t('reportRuntimeReadiness.metrics.lanes')} value={reportRuntimeLanes} tone="warn" icon={<Route size={16} />} />
<MetricCard label={t('reportRuntimeReadiness.metrics.ready')} value={reportRuntimeReady} tone="ok" icon={<ShieldCheck size={16} />} />
<MetricCard label={t('reportRuntimeReadiness.metrics.blocked')} value={reportRuntimeBlocked} tone={reportRuntimeBlocked > 0 ? 'danger' : 'ok'} icon={<ShieldAlert size={16} />} />
<MetricCard label={t('reportRuntimeReadiness.metrics.approvals')} value={reportRuntimeApprovals} tone={reportRuntimeApprovals > 0 ? 'danger' : 'ok'} icon={<Lock size={16} />} />
<MetricCard label={t('reportRuntimeReadiness.metrics.enabled')} value={reportRuntimeEnabled} tone={reportRuntimeEnabled === 0 ? 'warn' : 'ok'} icon={<Database size={16} />} />
<MetricCard label={t('reportRuntimeReadiness.metrics.delivery')} value={reportRuntimeLiveDelivery} tone={reportRuntimeLiveDelivery === 0 ? 'warn' : 'ok'} icon={<BellRing size={16} />} />
<MetricCard label={t('reportRuntimeReadiness.metrics.mediumLow')} value={reportRuntimeMediumLowRuns} tone={reportRuntimeMediumLowRuns === 0 ? 'warn' : 'ok'} icon={<Target size={16} />} />
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: 12 }} className="automation-inventory-live-read-grid">
<div style={{ padding: 11, border: '0.5px solid #d8e8f1', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
<SmallLabel>{t('reportRuntimeReadiness.truthTitle')}</SmallLabel>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#64727a', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
{reportRuntimeReadiness.activation_truth.truth_note}
</span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t('reportRuntimeReadiness.flags.scheduler', { value: String(reportRuntimeReadiness.activation_truth.report_scheduler_contract_ready) })} />
<Chip value={t('reportRuntimeReadiness.flags.queue', { value: reportRuntimeQueueWrites })} muted />
<Chip value={t('reportRuntimeReadiness.flags.aiRuns', { value: reportRuntimeAiRuns })} muted />
<Chip value={t('reportRuntimeReadiness.flags.mediumLow', { value: reportRuntimeMediumLowRuns })} muted />
</div>
</div>
<div style={{ padding: 11, border: '0.5px solid #d8e8f1', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
<SmallLabel>{t('reportRuntimeReadiness.telegramTitle')}</SmallLabel>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#64727a', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
{t('reportRuntimeReadiness.telegramSummary', {
room: reportRuntimeReadiness.telegram_route_readiness.canonical_room,
secret: reportRuntimeReadiness.telegram_route_readiness.secret_ref,
blocked: reportRuntimeReadiness.telegram_route_readiness.blocked_route_count,
})}
</span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t('reportRuntimeReadiness.flags.gateway', { value: String(reportRuntimeReadiness.telegram_route_readiness.gateway_required) })} />
<Chip value={t('reportRuntimeReadiness.flags.directApi', { value: String(reportRuntimeReadiness.telegram_route_readiness.direct_bot_api_allowed) })} muted />
<Chip value={t('reportRuntimeReadiness.flags.deliveryVerified', { value: String(reportRuntimeReadiness.telegram_route_readiness.e2e_delivery_verified) })} muted />
</div>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
{visibleReportRuntimeLanes.map(lane => {
const tone = lane.contract_status === 'blocked_by_runtime_gate' ? 'danger' : lane.risk_tier === 'high' || lane.risk_tier === 'critical' ? 'warn' : 'ok'
return (
<div key={lane.lane_id} style={{ padding: 10, border: '0.5px solid #d8e8f1', borderRadius: 7, background: '#fff', 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' }}>
{lane.display_name}
</span>
<Chip value={redisDryRunValueLabel('agents', lane.owner_agent)} muted />
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t(`reportRuntimeReadiness.statuses.${lane.contract_status}` as never)} muted={tone !== 'danger'} />
<Chip value={t(`reportRuntimeReadiness.riskTiers.${lane.risk_tier}` as never)} muted={tone === 'ok'} />
<Chip value={t('reportRuntimeReadiness.labels.liveCount', { count: lane.current_live_count_24h })} muted />
</div>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#64727a', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{lane.enablement_required.slice(0, 2).join(' / ')}
</span>
<Chip value={lane.target_runtime} muted />
</div>
)
})}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
{reportRuntimeReadiness.automation_policies.map(policy => (
<div key={policy.risk_id} style={{ padding: 10, border: '0.5px solid #d8e8f1', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 7, minWidth: 0 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center' }}>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413' }}>
{policy.display_name}
</span>
<Chip value={t('reportRuntimeReadiness.labels.enabled', { value: String(policy.current_execution_enabled) })} muted />
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t('reportRuntimeReadiness.labels.autoAfterGuard', { value: String(policy.auto_allowed_after_guard) })} muted={!policy.auto_allowed_after_guard} />
<Chip value={t('reportRuntimeReadiness.labels.approvalRequired', { value: String(policy.approval_required) })} muted={!policy.approval_required} />
</div>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#64727a', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{policy.reporting_rule}
</span>
</div>
))}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
{visibleReportRuntimeDecisions.map(decision => (
<div key={decision.decision_id} style={{ padding: 10, border: '0.5px solid #d8e8f1', borderRadius: 7, background: '#fff', 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' }}>
{decision.display_name}
</span>
<Chip value={redisDryRunValueLabel('agents', decision.owner_agent)} muted />
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t(`reportRuntimeReadiness.statuses.${decision.status}` as never)} />
<Chip value={t('reportRuntimeReadiness.labels.approvalRequired', { value: String(decision.approval_required) })} muted={!decision.approval_required} />
</div>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#64727a', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{decision.next_safe_step}
</span>
</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

@@ -327,6 +327,11 @@ export const apiClient = {
return handleResponse<AiAgentReportAutomationReviewSnapshot>(res)
},
async getAiAgentReportRuntimeReadiness() {
const res = await fetch(`${API_BASE_URL}/agents/agent-report-runtime-readiness`)
return handleResponse<AiAgentReportRuntimeReadinessSnapshot>(res)
},
async getAiAgentOwnerApprovedFixtureDryRun() {
const res = await fetch(`${API_BASE_URL}/agents/agent-owner-approved-fixture-dry-run`)
return handleResponse<AiAgentOwnerApprovedFixtureDryRunSnapshot>(res)
@@ -2074,6 +2079,128 @@ export interface AiAgentReportAutomationReviewSnapshot {
}
}
export interface AiAgentReportRuntimeReadinessSnapshot {
schema_version: 'ai_agent_report_runtime_readiness_v1'
generated_at: string
program_status: {
overall_completion_percent: number
current_priority: 'P0' | 'P1' | 'P2' | 'P3'
current_task_id: 'P2-403L'
next_task_id: string
read_only_mode: true
runtime_authority: 'report_runtime_readiness_only_no_live_delivery_or_optimization'
status_note: string
}
source_refs: string[]
activation_truth: {
report_scheduler_contract_ready: true
telegram_gateway_queue_contract_ready: true
telegram_delivery_receipt_contract_ready: true
ai_readback_analysis_contract_ready: true
medium_low_auto_guard_contract_ready: true
high_risk_approval_gate_contract_ready: true
live_report_delivery_enabled: false
live_report_delivery_count_24h: number
telegram_gateway_queue_write_enabled: false
telegram_gateway_queue_write_count_24h: number
report_read_receipt_write_enabled: false
report_read_receipt_count_24h: number
ai_analysis_runtime_enabled: false
ai_analysis_runtime_count_24h: number
medium_low_auto_worker_enabled: false
medium_low_auto_execution_count_24h: number
production_optimization_enabled: false
production_optimization_count_24h: number
high_risk_auto_execution_enabled: false
high_risk_auto_execution_count_24h: number
truth_note: string
}
runtime_lanes: Array<{
lane_id: string
display_name: string
owner_agent: 'openclaw' | 'hermes' | 'nemotron'
risk_tier: 'low' | 'medium' | 'high' | 'critical'
target_runtime: string
contract_status: 'ready_for_owner_review' | 'blocked_by_runtime_gate'
current_live_count_24h: number
enablement_required: string[]
blocked_actions: string[]
}>
automation_policies: Array<{
risk_id: 'low' | 'medium' | 'high' | 'critical'
display_name: string
approval_required: boolean
auto_allowed_after_guard: boolean
current_execution_enabled: false
required_guards: string[]
reporting_rule: string
}>
report_delivery_cadence_gates: Array<{
cadence_id: 'daily' | 'weekly' | 'monthly'
display_name: string
scheduler_source: string
telegram_digest_policy: string
recipient_room: string
dry_run_required: true
current_delivery_enabled: false
live_delivery_count_24h: number
}>
telegram_route_readiness: {
canonical_room: 'AwoooI SRE 戰情室'
secret_ref: 'SRE_GROUP_CHAT_ID'
gateway_required: true
direct_bot_api_allowed: false
bot_log_out_allowed: false
legacy_routes_must_converge: true
telegram_gateway_queue_write_enabled: false
e2e_delivery_verified: false
delivery_receipt_write_enabled: false
blocked_route_count: number
}
agent_post_report_actions: Array<{
agent_id: 'openclaw' | 'hermes' | 'nemotron'
display_name: string
after_report_responsibility: string
allowed_without_approval: string[]
blocked_until_approval: string[]
live_action_count_24h: number
}>
operator_decisions: Array<{
decision_id: string
display_name: string
risk_tier: 'low' | 'medium' | 'high' | 'critical'
owner_agent: 'openclaw' | 'hermes' | 'nemotron'
approval_required: boolean
status: 'ready_for_review' | 'blocked_by_runtime_gate' | 'approval_required'
why_it_matters: string
next_safe_step: string
}>
display_redaction_contract: {
redaction_required: true
raw_report_payload_display_allowed: false
private_reasoning_display_allowed: false
secret_value_display_allowed: false
work_window_transcript_display_allowed: false
allowed_display_fields: string[]
blocked_display_fields: string[]
}
rollups: {
runtime_lane_count: number
report_cadence_gate_count: number
operator_decision_count: number
automation_policy_count: number
ready_contract_count: number
blocked_contract_count: number
approval_required_decision_ids: string[]
current_enabled_count: number
live_report_delivery_count: number
live_ai_analysis_count: number
live_medium_low_auto_execution_count: number
telegram_gateway_queue_write_count: number
high_risk_auto_execution_count: number
}
}
export interface AiAgentOwnerApprovedFixtureDryRunSnapshot {
schema_version: 'ai_agent_owner_approved_fixture_dry_run_v1'
generated_at: string