feat(mcp): run governed version lifecycle receipts

This commit is contained in:
ogt
2026-07-15 02:16:48 +08:00
parent b49a1ec164
commit aed4b044d4
13 changed files with 1831 additions and 36 deletions

View File

@@ -77,6 +77,39 @@ interface RuntimeReadback {
total_chunks: number
sources: number
}
version_lifecycle?: {
status: string
controller_configured: boolean
controller_owner: string
lock_backend?: string
interval_seconds: number
latest_run?: {
run_id: string
trace_id: string
work_item_id: string
status: string
candidate_count: number
observed_count: number
baseline_count: number
changed_count: number
policy_blocked_count: number
error_count: number
started_at: string
ended_at?: string | null
fresh: boolean
} | null
observations?: Array<{
candidate_id: string
catalog_source: string
observed_version?: string | null
discovery_status: string
change_state: string
lifecycle_terminal: string
error_class?: string | null
observed_at: string
}>
blockers?: string[]
}
federated_product_runtime_receipt_count?: number
federated_product_runtime_receipt_expected?: number
}
@@ -226,6 +259,13 @@ export default function McpControlPlanePage({ params }: { params: { locale: stri
const runtime = overview?.runtime
const gateway = runtime?.gateway_audit_24h
const rag = runtime?.rag
const lifecycleRuntime = runtime?.version_lifecycle
const lifecycleObservationByCandidate = useMemo(
() => new Map(
(lifecycleRuntime?.observations ?? []).map(row => [row.candidate_id, row]),
),
[lifecycleRuntime?.observations],
)
return (
<AppLayout locale={params.locale}>
@@ -440,7 +480,14 @@ export default function McpControlPlanePage({ params }: { params: { locale: stri
<td className="px-3 py-3 text-[#5f5b54]">{item.catalog_source}</td>
<td className="px-3 py-3"><StatusBadge value={item.decision} /></td>
<td className="px-3 py-3 text-[#5f5b54]">{item.scope}</td>
<td className="px-3 py-3 text-[#5f5b54]">{item.version_state}</td>
<td className="px-3 py-3 text-[#5f5b54]">
<div>{lifecycleObservationByCandidate.get(item.capability_id)?.observed_version ?? item.version_state}</div>
{lifecycleObservationByCandidate.has(item.capability_id) ? (
<div className="mt-1 text-[10px] text-[#8a867e]">
{lifecycleObservationByCandidate.get(item.capability_id)?.change_state.replaceAll('_', ' ')}
</div>
) : null}
</td>
<td className="px-3 py-3 text-[#5f5b54]">{item.digest_state} / {item.sbom_state} / {item.signature_state}</td>
<td className="px-3 py-3">
{item.deployment_allowed ? <CheckCircle2 className="h-4 w-4 text-[#2f7d54]" aria-label={t('external.allowed')} /> : <XCircle className="h-4 w-4 text-[#b2432d]" aria-label={t('external.blocked')} />}
@@ -470,6 +517,25 @@ export default function McpControlPlanePage({ params }: { params: { locale: stri
<div role="status" className="mt-4 rounded-xl border border-[#e8c77a] bg-[#fff9e9] p-3 text-xs leading-5 text-[#705219]">
{t('lifecycle.runtimeState', { value: overview.version_lifecycle.runtime_state })}
</div>
{lifecycleRuntime?.latest_run ? (
<div className="mt-3 rounded-xl border border-[#c8dfcf] bg-[#f2faf4] p-3 text-xs text-[#285a37]">
<div className="flex flex-wrap items-center justify-between gap-2">
<span className="font-bold">{t('lifecycle.latestReceipt')}</span>
<StatusBadge value={lifecycleRuntime.latest_run.status} />
</div>
<dl className="mt-3 grid grid-cols-2 gap-3 sm:grid-cols-4">
<div><dt className="text-[#66816d]">{t('lifecycle.observed')}</dt><dd className="mt-1 text-lg font-bold">{lifecycleRuntime.latest_run.observed_count}/{lifecycleRuntime.latest_run.candidate_count}</dd></div>
<div><dt className="text-[#66816d]">{t('lifecycle.changed')}</dt><dd className="mt-1 text-lg font-bold">{lifecycleRuntime.latest_run.changed_count}</dd></div>
<div><dt className="text-[#66816d]">{t('lifecycle.blocked')}</dt><dd className="mt-1 text-lg font-bold">{lifecycleRuntime.latest_run.policy_blocked_count}</dd></div>
<div><dt className="text-[#66816d]">{t('lifecycle.errors')}</dt><dd className="mt-1 text-lg font-bold">{lifecycleRuntime.latest_run.error_count}</dd></div>
</dl>
<p className="mt-3 break-all font-mono text-[10px] text-[#66816d]">{lifecycleRuntime.latest_run.work_item_id} · {lifecycleRuntime.latest_run.run_id}</p>
</div>
) : (
<p className="mt-3 rounded-xl border border-[#dedbd1] bg-[#fbfaf6] p-3 text-xs text-[#77736a]">
{t('lifecycle.pendingReceipt')}
</p>
)}
</article>
<article className="rounded-2xl border border-[#dedbd1] bg-white p-5" aria-labelledby="security-title">