feat(governance): 顯示 runtime readback fixture 批准包
This commit is contained in:
@@ -3339,6 +3339,8 @@
|
||||
"analysisTitle": "報告後 AI 解法與風險邊界",
|
||||
"deliveryTitle": "Telegram 報告實發批准包",
|
||||
"deliveryGuardTitle": "路由鎖 / 無發送回執保護",
|
||||
"runtimeFixtureTitle": "Runtime readback fixture 批准包",
|
||||
"runtimeFixtureGuardTitle": "Adapter / Verifier / Blocker 無寫入保護",
|
||||
"metrics": {
|
||||
"reports": "報告完成",
|
||||
"charts": "圖表區塊",
|
||||
@@ -3360,6 +3362,21 @@
|
||||
"botApi": "Bot API",
|
||||
"receiptWrite": "回執寫入",
|
||||
"note": "真相註記"
|
||||
},
|
||||
"runtimeFixtureMetrics": {
|
||||
"cards": "Fixture 卡",
|
||||
"adapters": "Adapter 合約",
|
||||
"verifiers": "Verifier 檢查",
|
||||
"blockers": "Blocker 映射",
|
||||
"liveQuery": "Live query 次數"
|
||||
},
|
||||
"runtimeFixtureTruth": {
|
||||
"targetRead": "Target 讀取",
|
||||
"runtimeReadback": "Runtime readback",
|
||||
"resultCapture": "Result capture 寫入",
|
||||
"productionWrite": "Production 寫入",
|
||||
"ownerApproval": "Owner 批准",
|
||||
"note": "真相註記"
|
||||
}
|
||||
},
|
||||
"metrics": {
|
||||
|
||||
@@ -3339,6 +3339,8 @@
|
||||
"analysisTitle": "報告後 AI 解法與風險邊界",
|
||||
"deliveryTitle": "Telegram 報告實發批准包",
|
||||
"deliveryGuardTitle": "路由鎖 / 無發送回執保護",
|
||||
"runtimeFixtureTitle": "Runtime readback fixture 批准包",
|
||||
"runtimeFixtureGuardTitle": "Adapter / Verifier / Blocker 無寫入保護",
|
||||
"metrics": {
|
||||
"reports": "報告完成",
|
||||
"charts": "圖表區塊",
|
||||
@@ -3360,6 +3362,21 @@
|
||||
"botApi": "Bot API",
|
||||
"receiptWrite": "回執寫入",
|
||||
"note": "真相註記"
|
||||
},
|
||||
"runtimeFixtureMetrics": {
|
||||
"cards": "Fixture 卡",
|
||||
"adapters": "Adapter 合約",
|
||||
"verifiers": "Verifier 檢查",
|
||||
"blockers": "Blocker 映射",
|
||||
"liveQuery": "Live query 次數"
|
||||
},
|
||||
"runtimeFixtureTruth": {
|
||||
"targetRead": "Target 讀取",
|
||||
"runtimeReadback": "Runtime readback",
|
||||
"resultCapture": "Result capture 寫入",
|
||||
"productionWrite": "Production 寫入",
|
||||
"ownerApproval": "Owner 批准",
|
||||
"note": "真相註記"
|
||||
}
|
||||
},
|
||||
"metrics": {
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
apiClient,
|
||||
type AgentMarketGovernanceSnapshot,
|
||||
type AiAgentReportLiveDeliveryApprovalPackageSnapshot,
|
||||
type AiAgentRuntimeReadbackFixtureApprovalSnapshot,
|
||||
type AiTechnologyRadarReadback,
|
||||
type AiTechnologyReportCadenceReadback,
|
||||
} from '@/lib/api-client'
|
||||
@@ -42,12 +43,12 @@ function formatPercent(value: number): string {
|
||||
}
|
||||
|
||||
const TECHNOLOGY_AREA_LABELS: Record<string, string> = {
|
||||
agent_frameworks: 'Agent Frameworks',
|
||||
evaluation_and_observability: 'Eval / Observability',
|
||||
agent_frameworks: 'Agent 框架',
|
||||
evaluation_and_observability: '評測 / 可觀測性',
|
||||
mcp_and_a2a: 'MCP / A2A',
|
||||
model_providers: 'Model Providers',
|
||||
model_serving: 'Model Serving',
|
||||
rag_and_vector: 'RAG / Vector',
|
||||
model_providers: '模型 Provider',
|
||||
model_serving: '模型服務',
|
||||
rag_and_vector: 'RAG / 向量',
|
||||
}
|
||||
|
||||
function formatTechnologyArea(value: string): string {
|
||||
@@ -425,13 +426,13 @@ function PostReportAnalysisCard({ packet }: { packet: AiTechnologyReportCadenceR
|
||||
{packet.risk_tier}
|
||||
</span>
|
||||
</div>
|
||||
<DetailRow label="FINDING">
|
||||
<DetailRow label="關鍵發現">
|
||||
{packet.key_finding}
|
||||
</DetailRow>
|
||||
<DetailRow label="SOLUTION">
|
||||
<DetailRow label="解決方案">
|
||||
{packet.proposed_solution}
|
||||
</DetailRow>
|
||||
<DetailRow label="BOUNDARY">
|
||||
<DetailRow label="執行邊界">
|
||||
{packet.execution_boundary}
|
||||
</DetailRow>
|
||||
</div>
|
||||
@@ -564,6 +565,166 @@ function DryRunReceiptCard({ receipt }: { receipt: AiAgentReportLiveDeliveryAppr
|
||||
)
|
||||
}
|
||||
|
||||
function RuntimeFixtureApprovalCard({ card }: { card: AiAgentRuntimeReadbackFixtureApprovalSnapshot['fixture_approval_cards'][number] }) {
|
||||
const tone = card.risk_tier === 'critical' ? '#DC2626' : card.risk_tier === 'high' ? '#F59E0B' : '#d97757'
|
||||
return (
|
||||
<div style={{
|
||||
minWidth: 0,
|
||||
padding: 12,
|
||||
border: '0.5px solid #e0ddd4',
|
||||
borderRadius: 7,
|
||||
background: '#fff',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 9,
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 10, alignItems: 'flex-start', minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{card.display_name}
|
||||
</span>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
|
||||
<CandidatePill value={card.source_task_id} />
|
||||
<CandidatePill value={card.owner_agent} muted />
|
||||
<CandidatePill value={card.status} muted={card.status !== 'blocked_by_policy'} />
|
||||
<CandidatePill value={`fixture_only=${card.fixture_only}`} muted />
|
||||
</div>
|
||||
</div>
|
||||
<span style={{
|
||||
fontFamily: "'DM Mono', monospace",
|
||||
fontSize: 10,
|
||||
fontWeight: 700,
|
||||
color: tone,
|
||||
textTransform: 'uppercase',
|
||||
whiteSpace: 'nowrap',
|
||||
}}>
|
||||
{card.risk_tier}
|
||||
</span>
|
||||
</div>
|
||||
<DetailRow label="fixture 欄位">
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
|
||||
{card.required_fixture_fields.map(field => (
|
||||
<CandidatePill key={field} value={field} muted />
|
||||
))}
|
||||
</div>
|
||||
</DetailRow>
|
||||
<DetailRow label="阻擋 runtime 動作">
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
|
||||
{card.blocked_runtime_actions.map(action => (
|
||||
<CandidatePill key={action} value={action} muted />
|
||||
))}
|
||||
</div>
|
||||
</DetailRow>
|
||||
<DetailRow label="操作指引">
|
||||
{card.operator_guidance}
|
||||
</DetailRow>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function AdapterContractCard({ contract }: { contract: AiAgentRuntimeReadbackFixtureApprovalSnapshot['adapter_contracts'][number] }) {
|
||||
return (
|
||||
<div style={{
|
||||
minWidth: 0,
|
||||
padding: 12,
|
||||
border: '0.5px solid #e0ddd4',
|
||||
borderRadius: 7,
|
||||
background: '#fff',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 9,
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 10, alignItems: 'center', minWidth: 0 }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{contract.display_name}
|
||||
</span>
|
||||
<CandidatePill value={contract.status} muted={contract.status !== 'blocked_by_policy'} />
|
||||
</div>
|
||||
<DetailRow label="Schema 對接">
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
|
||||
<CandidatePill value={contract.input_schema} muted />
|
||||
<CandidatePill value={contract.output_schema} muted />
|
||||
</div>
|
||||
</DetailRow>
|
||||
<DetailRow label="必要證據">
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
|
||||
{contract.required_evidence.map(evidence => (
|
||||
<CandidatePill key={evidence} value={evidence} muted />
|
||||
))}
|
||||
</div>
|
||||
</DetailRow>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
|
||||
<CandidatePill value={`target_read=${contract.canonical_target_read_enabled}`} muted />
|
||||
<CandidatePill value={`live_query=${contract.live_query_enabled}`} muted />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function VerifierFixtureCheckCard({ check }: { check: AiAgentRuntimeReadbackFixtureApprovalSnapshot['verifier_fixture_checks'][number] }) {
|
||||
return (
|
||||
<div style={{
|
||||
minWidth: 0,
|
||||
padding: 12,
|
||||
border: '0.5px solid #e0ddd4',
|
||||
borderRadius: 7,
|
||||
background: '#fff',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 9,
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 10, alignItems: 'center', minWidth: 0 }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{check.display_name}
|
||||
</span>
|
||||
<CandidatePill value={check.status} muted={check.status !== 'blocked_by_policy'} />
|
||||
</div>
|
||||
<DetailRow label="fixture 條件">
|
||||
{check.required_fixture}
|
||||
</DetailRow>
|
||||
<DetailRow label="缺漏時阻擋">
|
||||
{check.failure_if_missing}
|
||||
</DetailRow>
|
||||
<CandidatePill value={`live_verifier=${check.live_verifier_enabled}`} muted />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function RuntimeBlockerMappingCard({ blocker }: { blocker: AiAgentRuntimeReadbackFixtureApprovalSnapshot['blocker_mappings'][number] }) {
|
||||
const tone = blocker.severity === 'critical' ? '#DC2626' : blocker.severity === 'high' ? '#F59E0B' : '#d97757'
|
||||
return (
|
||||
<div style={{
|
||||
minWidth: 0,
|
||||
padding: 12,
|
||||
border: '0.5px solid #e0ddd4',
|
||||
borderRadius: 7,
|
||||
background: '#fff',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 9,
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 10, alignItems: 'flex-start', minWidth: 0 }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{blocker.display_name}
|
||||
</span>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, fontWeight: 700, color: tone, textTransform: 'uppercase', whiteSpace: 'nowrap' }}>
|
||||
{blocker.severity}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
|
||||
<CandidatePill value={blocker.status} muted={blocker.status !== 'blocked_by_policy'} />
|
||||
<CandidatePill value={blocker.blocked_action} muted />
|
||||
</div>
|
||||
<DetailRow label="來源 blocker">
|
||||
{blocker.source_blocker}
|
||||
</DetailRow>
|
||||
<DetailRow label="解除條件">
|
||||
{blocker.blocked_until}
|
||||
</DetailRow>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Component
|
||||
// =============================================================================
|
||||
@@ -574,6 +735,7 @@ export function AgentMarketTab() {
|
||||
const [technologyRadar, setTechnologyRadar] = useState<AiTechnologyRadarReadback | null>(null)
|
||||
const [technologyReportCadence, setTechnologyReportCadence] = useState<AiTechnologyReportCadenceReadback | null>(null)
|
||||
const [reportDeliveryApproval, setReportDeliveryApproval] = useState<AiAgentReportLiveDeliveryApprovalPackageSnapshot | null>(null)
|
||||
const [runtimeFixtureApproval, setRuntimeFixtureApproval] = useState<AiAgentRuntimeReadbackFixtureApprovalSnapshot | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
@@ -584,12 +746,14 @@ export function AgentMarketTab() {
|
||||
apiClient.getAiTechnologyRadarReadback(),
|
||||
apiClient.getAiTechnologyReportCadenceReadback(),
|
||||
apiClient.getAiAgentReportLiveDeliveryApprovalPackage(),
|
||||
apiClient.getAiAgentRuntimeReadbackFixtureApproval(),
|
||||
])
|
||||
.then(([marketSnapshot, radarReadback, reportCadenceReadback, deliveryApprovalPackage]) => {
|
||||
.then(([marketSnapshot, radarReadback, reportCadenceReadback, deliveryApprovalPackage, runtimeFixtureApprovalPackage]) => {
|
||||
setSnapshot(marketSnapshot)
|
||||
setTechnologyRadar(radarReadback)
|
||||
setTechnologyReportCadence(reportCadenceReadback)
|
||||
setReportDeliveryApproval(deliveryApprovalPackage)
|
||||
setRuntimeFixtureApproval(runtimeFixtureApprovalPackage)
|
||||
setError(false)
|
||||
})
|
||||
.catch(() => setError(true))
|
||||
@@ -614,7 +778,7 @@ export function AgentMarketTab() {
|
||||
)
|
||||
}
|
||||
|
||||
if (error || !snapshot || !technologyRadar || !technologyReportCadence || !reportDeliveryApproval) {
|
||||
if (error || !snapshot || !technologyRadar || !technologyReportCadence || !reportDeliveryApproval || !runtimeFixtureApproval) {
|
||||
return (
|
||||
<div style={{ padding: 20 }}>
|
||||
<GlassCard variant="subtle" padding="lg">
|
||||
@@ -652,6 +816,7 @@ export function AgentMarketTab() {
|
||||
const radarSummary = technologyRadar.summary
|
||||
const reportSummary = technologyReportCadence.summary
|
||||
const deliveryRollups = reportDeliveryApproval.rollups
|
||||
const fixtureRollups = runtimeFixtureApproval.rollups
|
||||
const allApprovals =
|
||||
summary.priority_upgrades_approved +
|
||||
summary.market_scorecard_updates_approved +
|
||||
@@ -772,6 +937,109 @@ export function AgentMarketTab() {
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard variant="subtle" padding="md">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 9, minWidth: 0 }}>
|
||||
<ShieldCheck size={15} style={{ color: '#d97757' }} />
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 14, fontWeight: 700, color: '#141413' }}>
|
||||
{t('technologyReports.runtimeFixtureTitle')}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, justifyContent: 'flex-end', minWidth: 0, maxWidth: '100%' }}>
|
||||
<CandidatePill value={runtimeFixtureApproval.program_status.current_task_id} />
|
||||
<CandidatePill value={runtimeFixtureApproval.program_status.runtime_authority} muted />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(5, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
}} className="agent-market-runtime-fixture-metrics-grid">
|
||||
<RadarStat label={t('technologyReports.runtimeFixtureMetrics.cards')} value={fixtureRollups.fixture_approval_card_count} />
|
||||
<RadarStat label={t('technologyReports.runtimeFixtureMetrics.adapters')} value={fixtureRollups.adapter_contract_count} />
|
||||
<RadarStat label={t('technologyReports.runtimeFixtureMetrics.verifiers')} value={fixtureRollups.verifier_fixture_check_count} />
|
||||
<RadarStat label={t('technologyReports.runtimeFixtureMetrics.blockers')} value={fixtureRollups.blocker_mapping_count} tone="warn" />
|
||||
<RadarStat label={t('technologyReports.runtimeFixtureMetrics.liveQuery')} value={fixtureRollups.live_query_count} tone="ok" />
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(5, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
}} className="agent-market-runtime-fixture-truth-grid">
|
||||
<DetailRow label={t('technologyReports.runtimeFixtureTruth.targetRead')}>
|
||||
<CandidatePill value={String(runtimeFixtureApproval.fixture_approval_truth.canonical_runtime_target_read_enabled)} muted />
|
||||
</DetailRow>
|
||||
<DetailRow label={t('technologyReports.runtimeFixtureTruth.runtimeReadback')}>
|
||||
<CandidatePill value={String(runtimeFixtureApproval.fixture_approval_truth.runtime_readback_execution_enabled)} muted />
|
||||
</DetailRow>
|
||||
<DetailRow label={t('technologyReports.runtimeFixtureTruth.resultCapture')}>
|
||||
<CandidatePill value={String(runtimeFixtureApproval.fixture_approval_truth.result_capture_write_enabled)} muted />
|
||||
</DetailRow>
|
||||
<DetailRow label={t('technologyReports.runtimeFixtureTruth.productionWrite')}>
|
||||
<CandidatePill value={String(runtimeFixtureApproval.fixture_approval_truth.production_write_enabled)} muted />
|
||||
</DetailRow>
|
||||
<DetailRow label={t('technologyReports.runtimeFixtureTruth.ownerApproval')}>
|
||||
<CandidatePill value={String(runtimeFixtureApproval.fixture_approval_truth.owner_approval_received_count)} muted />
|
||||
</DetailRow>
|
||||
</div>
|
||||
|
||||
<DetailRow label={t('technologyReports.runtimeFixtureTruth.note')}>
|
||||
{runtimeFixtureApproval.fixture_approval_truth.truth_note}
|
||||
</DetailRow>
|
||||
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
}} className="agent-market-runtime-fixture-card-grid">
|
||||
{runtimeFixtureApproval.fixture_approval_cards.map(card => (
|
||||
<RuntimeFixtureApprovalCard key={card.card_id} card={card} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard variant="subtle" padding="md">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 13, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
|
||||
<Lock size={14} style={{ color: '#F59E0B' }} />
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{t('technologyReports.runtimeFixtureGuardTitle')}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
}} className="agent-market-runtime-adapter-grid">
|
||||
{runtimeFixtureApproval.adapter_contracts.map(contract => (
|
||||
<AdapterContractCard key={contract.contract_id} contract={contract} />
|
||||
))}
|
||||
</div>
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
}} className="agent-market-runtime-verifier-grid">
|
||||
{runtimeFixtureApproval.verifier_fixture_checks.map(check => (
|
||||
<VerifierFixtureCheckCard key={check.check_id} check={check} />
|
||||
))}
|
||||
</div>
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
}} className="agent-market-runtime-blocker-grid">
|
||||
{runtimeFixtureApproval.blocker_mappings.map(blocker => (
|
||||
<RuntimeBlockerMappingCard key={blocker.blocker_id} blocker={blocker} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard variant="subtle" padding="md">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
|
||||
@@ -1430,6 +1698,12 @@ export function AgentMarketTab() {
|
||||
.agent-market-delivery-packet-grid,
|
||||
.agent-market-delivery-guard-grid,
|
||||
.agent-market-delivery-receipt-grid,
|
||||
.agent-market-runtime-fixture-metrics-grid,
|
||||
.agent-market-runtime-fixture-truth-grid,
|
||||
.agent-market-runtime-fixture-card-grid,
|
||||
.agent-market-runtime-adapter-grid,
|
||||
.agent-market-runtime-verifier-grid,
|
||||
.agent-market-runtime-blocker-grid,
|
||||
.agent-market-health-grid,
|
||||
.agent-market-cadence-grid,
|
||||
.agent-market-decision-grid,
|
||||
|
||||
Reference in New Issue
Block a user