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

@@ -187,7 +187,13 @@
"lifecycle": {
"title": "Version discovery to rollback loop",
"subtitle": "Target discovery cadence: {cadence}; every version diff must pass replay and canary.",
"runtimeState": "Current runtime state: {value}. Static policy is never presented as a running scheduler."
"runtimeState": "Current runtime state: {value}. Static policy is never presented as a running scheduler.",
"latestReceipt": "Latest durable execution receipt",
"observed": "Observed",
"changed": "Changed",
"blocked": "Policy blocked",
"errors": "Source errors",
"pendingReceipt": "The controller is scheduled and awaiting its first production runtime receipt."
},
"security": {
"title": "Enforced security boundaries",

View File

@@ -187,7 +187,13 @@
"lifecycle": {
"title": "版本發現到回滾循環",
"subtitle": "目標 discovery cadence{cadence};每次 version diff 都必須走完整 replay 與 canary。",
"runtimeState": "目前 runtime 狀態:{value}。此欄不會用靜態 policy 冒充已運行排程。"
"runtimeState": "目前 runtime 狀態:{value}。此欄不會用靜態 policy 冒充已運行排程。",
"latestReceipt": "最近一次持久化執行 receipt",
"observed": "已觀測",
"changed": "變更",
"blocked": "Policy 阻擋",
"errors": "來源錯誤",
"pendingReceipt": "控制器已排程,等待第一筆 production runtime receipt。"
},
"security": {
"title": "強制安全邊界",

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