feat(web): bind homepage blueprint to live evidence
All checks were successful
CD Pipeline / tests (push) Successful in 1m36s
Code Review / ai-code-review (push) Successful in 11s
CD Pipeline / build-and-deploy (push) Successful in 3m23s
CD Pipeline / post-deploy-checks (push) Successful in 1m24s

This commit is contained in:
Your Name
2026-05-26 11:25:14 +08:00
parent 8305454f37
commit 320718aa36
3 changed files with 485 additions and 5 deletions

View File

@@ -525,6 +525,57 @@
}
}
},
"liveEvidence": {
"title": "Live Evidence",
"realtime": "Live read",
"fields": {
"metric": "Metric",
"detail": "Detail",
"source": "Read Source"
},
"sources": {
"dossierCoverage": "/api/v1/platform/events/dossier/coverage",
"runsAndCicd": "/api/v1/platform/runs/list + /api/v1/platform/cicd/events",
"aiRouteStatus": "/api/v1/platform/ai-route-status",
"runsAndStatusChain": "/api/v1/platform/runs/list + /api/v1/platform/status-chain",
"qualityAndRecurrence": "/api/v1/platform/truth-chain/quality/summary + /api/v1/platform/events/dossier/recurrence",
"truthChainQuality": "/api/v1/platform/truth-chain/quality/summary",
"approvalsAndQuality": "approval store + /api/v1/platform/truth-chain/quality/summary",
"kmBurndown": "/api/v1/ai/governance/km-stale-owner-review-burndown"
},
"signal": {
"metric": "sources {sources} / refs {refs}",
"detail": "missing refs {missing}, duplicates {duplicates}; Alert {alert} / Sentry {sentry} / SigNoz {signoz}"
},
"intake": {
"metric": "Runs {runs} / linked {linked}",
"detail": "latest CI/CD {stage}:{status}, commit {commit}, needs attention {attention}"
},
"ai": {
"metric": "{lane} / {provider}",
"detail": "skipped lanes {skipped}, operator action={action}, reason={reason}"
},
"mcp": {
"metric": "MCP observations {observations} / gateway {gateway}",
"detail": "success {success}, failed {failed}, server={server}, route={route}"
},
"playbook": {
"metric": "gate {gate} / automation gaps {gaps}",
"detail": "open work items {workItems}, verified groups {verifiedGroups}, auto-repair linked {linkedAutoRepair}"
},
"ansible": {
"metric": "check-mode {checkMode} / pending {pending}",
"detail": "blocker={blocker}, candidates={candidates}, operations={operations}"
},
"approval": {
"metric": "pending {pending} / verified {verified}/{evaluated}",
"detail": "human gates {humanGates}, auto-repair records {autoRepairRecords}, operation records {operations}"
},
"verify": {
"metric": "stale {stale} / ratio {ratio}",
"detail": "owner review pending {pending}, completed {completed}, remaining to threshold {remaining}"
}
},
"values": {
"verified": "verified {verified}/{evaluated}",
"topGate": "{gate} missing {count}",

View File

@@ -526,6 +526,57 @@
}
}
},
"liveEvidence": {
"title": "Live Evidence",
"realtime": "即時讀取",
"fields": {
"metric": "指標",
"detail": "細節",
"source": "讀取來源"
},
"sources": {
"dossierCoverage": "/api/v1/platform/events/dossier/coverage",
"runsAndCicd": "/api/v1/platform/runs/list + /api/v1/platform/cicd/events",
"aiRouteStatus": "/api/v1/platform/ai-route-status",
"runsAndStatusChain": "/api/v1/platform/runs/list + /api/v1/platform/status-chain",
"qualityAndRecurrence": "/api/v1/platform/truth-chain/quality/summary + /api/v1/platform/events/dossier/recurrence",
"truthChainQuality": "/api/v1/platform/truth-chain/quality/summary",
"approvalsAndQuality": "approval store + /api/v1/platform/truth-chain/quality/summary",
"kmBurndown": "/api/v1/ai/governance/km-stale-owner-review-burndown"
},
"signal": {
"metric": "來源 {sources} / refs {refs}",
"detail": "missing refs {missing}duplicates {duplicates}Alert {alert} / Sentry {sentry} / SigNoz {signoz}"
},
"intake": {
"metric": "Runs {runs} / linked {linked}",
"detail": "最新 CI/CD {stage}:{status}commit {commit},需注意 {attention}"
},
"ai": {
"metric": "{lane} / {provider}",
"detail": "skipped lanes {skipped}operator action={action}reason={reason}"
},
"mcp": {
"metric": "MCP observations {observations} / gateway {gateway}",
"detail": "success {success}failed {failed}server={server}route={route}"
},
"playbook": {
"metric": "gate {gate} / automation gaps {gaps}",
"detail": "open work items {workItems}verified groups {verifiedGroups}auto-repair linked {linkedAutoRepair}"
},
"ansible": {
"metric": "check-mode {checkMode} / pending {pending}",
"detail": "blocker={blocker}candidates={candidates}operations={operations}"
},
"approval": {
"metric": "pending {pending} / verified {verified}/{evaluated}",
"detail": "human gates {humanGates}auto-repair records {autoRepairRecords}operation records {operations}"
},
"verify": {
"metric": "stale {stale} / ratio {ratio}",
"detail": "owner review pending {pending}completed {completed},距離門檻剩 {remaining}"
}
},
"values": {
"verified": "verified {verified}/{evaluated}",
"topGate": "{gate} 缺 {count}",

View File

@@ -104,10 +104,97 @@ interface HomepageKmStaleCandidatesResponse {
threshold_days?: number
}
interface HomepageDossierCoverageResponse {
summary?: {
source_count?: number
source_ref_total?: number
missing_source_refs_total?: number
duplicate_total?: number
alert_ref_total?: number
sentry_ref_total?: number
signoz_ref_total?: number
latest_received_at?: string | null
}
}
interface HomepageEventRecurrenceResponse {
summary?: {
source_event_total?: number
linked_run_total?: number
unlinked_event_total?: number
auto_repair_linked_total?: number
verified_repair_group_total?: number
open_work_item_group_total?: number
automation_gap_group_total?: number
recurrent_group_total?: number
recurrence_group_total?: number
duplicate_event_total?: number
latest_received_at?: string | null
}
}
interface HomepageRunSummary {
run_id?: string
state?: string
created_at?: string | null
remediation_summary?: {
status?: string | null
evidence_total?: number | null
mcp_observation_total?: number | null
mcp_observation_success?: number | null
mcp_observation_failed?: number | null
latest_mcp_server?: string | null
latest_route?: string | null
has_mcp_investigation?: boolean | null
has_dry_run?: boolean | null
human_gate_open?: boolean | null
} | null
}
interface HomepageRunsListResponse {
runs?: HomepageRunSummary[]
total?: number
}
interface HomepageCicdEventsResponse {
total?: number
items?: Array<{
stage?: string | null
status?: string | null
summary?: string | null
commit_sha?: string | null
needs_attention?: boolean | null
created_at?: string | null
}>
}
interface HomepageKmOwnerReviewBurndownResponse {
burn_down_status?: string | null
current_snapshot?: {
stale_count?: number
total_count?: number
stale_ratio?: number
threshold?: number
stale_days?: number
} | null
entries_to_threshold?: number | null
pending_owner_reviews?: number | null
completed_owner_reviews?: number | null
completion_audit_total?: number | null
stale_ratio_recheck_total?: number | null
manual_review_required?: boolean | null
generated_at?: string | null
}
interface HomepageAutomationBriefSnapshot {
callbackReplies?: HomepageCallbackRepliesResponse | null
aiRouteStatus?: HomepageAiRouteStatusResponse | null
kmStaleCandidates?: HomepageKmStaleCandidatesResponse | null
dossierCoverage?: HomepageDossierCoverageResponse | null
eventRecurrence?: HomepageEventRecurrenceResponse | null
runsList?: HomepageRunsListResponse | null
cicdEvents?: HomepageCicdEventsResponse | null
kmOwnerReviewBurndown?: HomepageKmOwnerReviewBurndownResponse | null
}
type HomepageWorkTone = 'live' | 'progress' | 'blocked' | 'watching'
@@ -125,6 +212,12 @@ interface HomepageBlueprintStage extends HomepageWorkItemSummary {
owner: string
evidence: string
nextAction: string
liveEvidence: {
metric: string
detail: string
source: string
updated: string
}
}
async function fetchHomepageJson<T>(url: string, signal?: AbortSignal): Promise<T | null> {
@@ -689,8 +782,46 @@ export default function Home({ params }: { params: { locale: string } }) {
`${API_BASE}/api/v1/ai/governance/km-stale-candidates?project_id=${encodedProjectId}&limit=5`,
controller.signal
),
]).then(([callbackReplies, aiRouteStatus, kmStaleCandidates]) => {
setAutomationBrief({ callbackReplies, aiRouteStatus, kmStaleCandidates })
fetchHomepageJson<HomepageDossierCoverageResponse>(
`${API_BASE}/api/v1/platform/events/dossier/coverage?project_id=${encodedProjectId}&limit=100`,
controller.signal
),
fetchHomepageJson<HomepageEventRecurrenceResponse>(
`${API_BASE}/api/v1/platform/events/dossier/recurrence?project_id=${encodedProjectId}&limit=100`,
controller.signal
),
fetchHomepageJson<HomepageRunsListResponse>(
`${API_BASE}/api/v1/platform/runs/list?project_id=${encodedProjectId}&per_page=25`,
controller.signal
),
fetchHomepageJson<HomepageCicdEventsResponse>(
`${API_BASE}/api/v1/platform/cicd/events?project_id=${encodedProjectId}&limit=12`,
controller.signal
),
fetchHomepageJson<HomepageKmOwnerReviewBurndownResponse>(
`${API_BASE}/api/v1/ai/governance/km-stale-owner-review-burndown?project_id=${encodedProjectId}&limit=5`,
controller.signal
),
]).then(([
callbackReplies,
aiRouteStatus,
kmStaleCandidates,
dossierCoverage,
eventRecurrence,
runsList,
cicdEvents,
kmOwnerReviewBurndown,
]) => {
setAutomationBrief({
callbackReplies,
aiRouteStatus,
kmStaleCandidates,
dossierCoverage,
eventRecurrence,
runsList,
cicdEvents,
kmOwnerReviewBurndown,
})
}).catch(() => {})
.finally(() => setAutomationBriefLoaded(true))
return () => controller.abort()
@@ -701,6 +832,17 @@ export default function Home({ params }: { params: { locale: string } }) {
const unavailableValue = tDashboard('automationDelivery.unavailableValue')
const loadingStatus = tDashboard('automationDelivery.status.loading')
const unavailableStatus = tDashboard('automationDelivery.status.unavailable')
const formatLiveEvidenceTime = (value: string | null | undefined) => {
if (!value) return automationBriefLoaded ? unavailableValue : loadingStatus
const date = new Date(value)
if (Number.isNaN(date.getTime())) return unavailableValue
return date.toLocaleString(locale === 'en' ? 'en-US' : 'zh-TW', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
})
}
const evaluatedAutomationTotal = automationQuality?.evaluated_total
const verifiedAutomationTotal = automationQuality?.verified_auto_repair_total
const formatAutomationNumber = (value: number | null | undefined, loaded: boolean) => {
@@ -753,6 +895,66 @@ export default function Home({ params }: { params: { locale: string } }) {
const hasAiRouteStatus = automationBrief.aiRouteStatus !== undefined && automationBrief.aiRouteStatus !== null
const hasKmStaleCandidates = automationBrief.kmStaleCandidates !== undefined && automationBrief.kmStaleCandidates !== null
const canClaimFullAutoRepair = automationQualityAvailable && Boolean(automationQuality?.production_claim?.can_claim_full_auto_repair)
const dossierCoverageSummary = automationBrief.dossierCoverage?.summary ?? null
const recurrenceSummary = automationBrief.eventRecurrence?.summary ?? null
const recentRuns = automationBrief.runsList?.runs ?? []
const latestRun = recentRuns[0]
const runEvidenceSummary = recentRuns.reduce(
(acc, run) => {
const summary = run.remediation_summary
acc.mcpTotal += summary?.mcp_observation_total ?? 0
acc.mcpSuccess += summary?.mcp_observation_success ?? 0
acc.mcpFailed += summary?.mcp_observation_failed ?? 0
acc.evidenceTotal += summary?.evidence_total ?? 0
if (summary?.has_mcp_investigation) acc.mcpInvestigatedRuns += 1
if (summary?.human_gate_open) acc.humanGateRuns += 1
if (!acc.latestMcpServer && summary?.latest_mcp_server) acc.latestMcpServer = summary.latest_mcp_server
if (!acc.latestRoute && summary?.latest_route) acc.latestRoute = summary.latest_route
return acc
},
{
mcpTotal: 0,
mcpSuccess: 0,
mcpFailed: 0,
evidenceTotal: 0,
mcpInvestigatedRuns: 0,
humanGateRuns: 0,
latestMcpServer: null as string | null,
latestRoute: null as string | null,
}
)
const statusChainValues = Object.values(statusChains).filter((chain): chain is NonNullable<typeof chain> => Boolean(chain && !chain.fetch_error))
const statusChainEvidenceSummary = statusChainValues.reduce(
(acc, chain) => {
acc.mcpGateway += chain.mcp?.gateway?.total ?? chain.evidence?.mcp_gateway_total ?? 0
acc.mcpGatewaySuccess += chain.mcp?.gateway?.success ?? 0
acc.mcpGatewayFailed += chain.mcp?.gateway?.failed ?? 0
acc.operationRecords += chain.evidence?.operation_records ?? chain.execution?.operation_total ?? 0
acc.autoRepairRecords += chain.evidence?.auto_repair_records ?? 0
acc.knowledgeEntries += chain.evidence?.knowledge_entries ?? 0
acc.inboundRefs += chain.source_refs?.inbound_total ?? 0
acc.outboundRefs += chain.source_refs?.outbound_total ?? 0
return acc
},
{
mcpGateway: 0,
mcpGatewaySuccess: 0,
mcpGatewayFailed: 0,
operationRecords: 0,
autoRepairRecords: 0,
knowledgeEntries: 0,
inboundRefs: 0,
outboundRefs: 0,
}
)
const cicdItems = automationBrief.cicdEvents?.items ?? []
const latestCicdEvent = cicdItems[0]
const cicdNeedsAttention = cicdItems.filter(item => item.needs_attention).length
const kmOwnerReviewBurndown = automationBrief.kmOwnerReviewBurndown ?? null
const kmBurndownSnapshot = kmOwnerReviewBurndown?.current_snapshot ?? null
const staleRatioLabel = typeof kmBurndownSnapshot?.stale_ratio === 'number'
? `${(kmBurndownSnapshot.stale_ratio * 100).toFixed(1)}%`
: automationBriefLoaded ? unavailableValue : loadingStatus
const automationWorkToneStyle: Record<HomepageWorkTone, { bg: string; border: string; color: string }> = {
live: { bg: '#f0faf2', border: '#9bc7a4', color: '#17602a' },
progress: { bg: '#fff7e8', border: '#d9b36f', color: '#8a5a08' },
@@ -986,6 +1188,21 @@ export default function Home({ params }: { params: { locale: string } }) {
owner: tDashboard('automationDiagrams.workspace.inspector.stages.signal.owner'),
evidence: tDashboard('automationDiagrams.workspace.inspector.stages.signal.evidence'),
nextAction: tDashboard('automationDiagrams.workspace.inspector.stages.signal.nextAction'),
liveEvidence: {
metric: tDashboard('automationDiagrams.workspace.liveEvidence.signal.metric', {
sources: formatAutomationNumber(dossierCoverageSummary?.source_count, automationBriefLoaded),
refs: formatAutomationNumber(dossierCoverageSummary?.source_ref_total, automationBriefLoaded),
}),
detail: tDashboard('automationDiagrams.workspace.liveEvidence.signal.detail', {
missing: formatAutomationNumber(dossierCoverageSummary?.missing_source_refs_total, automationBriefLoaded),
duplicates: formatAutomationNumber(dossierCoverageSummary?.duplicate_total, automationBriefLoaded),
alert: formatAutomationNumber(dossierCoverageSummary?.alert_ref_total, automationBriefLoaded),
sentry: formatAutomationNumber(dossierCoverageSummary?.sentry_ref_total, automationBriefLoaded),
signoz: formatAutomationNumber(dossierCoverageSummary?.signoz_ref_total, automationBriefLoaded),
}),
source: tDashboard('automationDiagrams.workspace.liveEvidence.sources.dossierCoverage'),
updated: formatLiveEvidenceTime(dossierCoverageSummary?.latest_received_at),
},
},
{
key: 'intake',
@@ -997,6 +1214,20 @@ export default function Home({ params }: { params: { locale: string } }) {
owner: tDashboard('automationDiagrams.workspace.inspector.stages.intake.owner'),
evidence: tDashboard('automationDiagrams.workspace.inspector.stages.intake.evidence'),
nextAction: tDashboard('automationDiagrams.workspace.inspector.stages.intake.nextAction'),
liveEvidence: {
metric: tDashboard('automationDiagrams.workspace.liveEvidence.intake.metric', {
runs: formatAutomationNumber(automationBrief.runsList?.total, automationBriefLoaded),
linked: formatAutomationNumber(recurrenceSummary?.linked_run_total, automationBriefLoaded),
}),
detail: tDashboard('automationDiagrams.workspace.liveEvidence.intake.detail', {
stage: latestCicdEvent?.stage ?? (automationBriefLoaded ? unavailableValue : loadingStatus),
status: latestCicdEvent?.status ?? (automationBriefLoaded ? unavailableValue : loadingStatus),
commit: latestCicdEvent?.commit_sha?.slice(0, 7) ?? (automationBriefLoaded ? unavailableValue : loadingStatus),
attention: formatAutomationNumber(cicdNeedsAttention, automationBriefLoaded),
}),
source: tDashboard('automationDiagrams.workspace.liveEvidence.sources.runsAndCicd'),
updated: formatLiveEvidenceTime(latestCicdEvent?.created_at ?? latestRun?.created_at),
},
},
{
key: 'ai',
@@ -1011,17 +1242,49 @@ export default function Home({ params }: { params: { locale: string } }) {
owner: tDashboard('automationDiagrams.workspace.inspector.stages.ai.owner'),
evidence: tDashboard('automationDiagrams.workspace.inspector.stages.ai.evidence'),
nextAction: tDashboard('automationDiagrams.workspace.inspector.stages.ai.nextAction'),
liveEvidence: {
metric: tDashboard('automationDiagrams.workspace.liveEvidence.ai.metric', {
lane: aiRouteLaneMode,
provider: aiRouteSelectedProvider,
}),
detail: tDashboard('automationDiagrams.workspace.liveEvidence.ai.detail', {
skipped: formatAutomationNumber(automationBrief.aiRouteStatus?.skipped_lanes?.length, automationBriefLoaded),
action: automationBrief.aiRouteStatus?.operator_action?.action ?? (automationBriefLoaded ? unavailableValue : loadingStatus),
reason: automationBrief.aiRouteStatus?.operator_action?.reason ?? (automationBriefLoaded ? unavailableValue : loadingStatus),
}),
source: tDashboard('automationDiagrams.workspace.liveEvidence.sources.aiRouteStatus'),
updated: automationBriefLoaded ? tDashboard('automationDiagrams.workspace.liveEvidence.realtime') : loadingStatus,
},
},
{
key: 'mcp',
title: tDashboard('automationDiagrams.workspace.flow.stages.mcp'),
status: tDashboard('automationDelivery.status.watching'),
detail: tDashboard('automationDiagrams.cards.incidentFlow.detail'),
status: runEvidenceSummary.mcpTotal > 0 || statusChainEvidenceSummary.mcpGateway > 0
? tDashboard('automationDelivery.status.live')
: tDashboard('automationDelivery.status.watching'),
detail: tDashboard('automationDiagrams.workspace.liveEvidence.mcp.metric', {
observations: formatAutomationNumber(runEvidenceSummary.mcpTotal, automationBriefLoaded),
gateway: formatAutomationNumber(statusChainEvidenceSummary.mcpGateway, true),
}),
href: `/${locale}/awooop/runs?project_id=awoooi`,
tone: 'watching',
tone: runEvidenceSummary.mcpFailed > 0 ? 'progress' : (runEvidenceSummary.mcpTotal > 0 || statusChainEvidenceSummary.mcpGateway > 0) ? 'live' : 'watching',
owner: tDashboard('automationDiagrams.workspace.inspector.stages.mcp.owner'),
evidence: tDashboard('automationDiagrams.workspace.inspector.stages.mcp.evidence'),
nextAction: tDashboard('automationDiagrams.workspace.inspector.stages.mcp.nextAction'),
liveEvidence: {
metric: tDashboard('automationDiagrams.workspace.liveEvidence.mcp.metric', {
observations: formatAutomationNumber(runEvidenceSummary.mcpTotal, automationBriefLoaded),
gateway: formatAutomationNumber(statusChainEvidenceSummary.mcpGateway, true),
}),
detail: tDashboard('automationDiagrams.workspace.liveEvidence.mcp.detail', {
success: formatAutomationNumber(runEvidenceSummary.mcpSuccess + statusChainEvidenceSummary.mcpGatewaySuccess, automationBriefLoaded),
failed: formatAutomationNumber(runEvidenceSummary.mcpFailed + statusChainEvidenceSummary.mcpGatewayFailed, automationBriefLoaded),
server: runEvidenceSummary.latestMcpServer ?? (automationBriefLoaded ? unavailableValue : loadingStatus),
route: runEvidenceSummary.latestRoute ?? (automationBriefLoaded ? unavailableValue : loadingStatus),
}),
source: tDashboard('automationDiagrams.workspace.liveEvidence.sources.runsAndStatusChain'),
updated: formatLiveEvidenceTime(latestRun?.created_at),
},
},
{
key: 'playbook',
@@ -1036,6 +1299,19 @@ export default function Home({ params }: { params: { locale: string } }) {
owner: tDashboard('automationDiagrams.workspace.inspector.stages.playbook.owner'),
evidence: tDashboard('automationDiagrams.workspace.inspector.stages.playbook.evidence'),
nextAction: tDashboard('automationDiagrams.workspace.inspector.stages.playbook.nextAction'),
liveEvidence: {
metric: tDashboard('automationDiagrams.workspace.liveEvidence.playbook.metric', {
gate: topAutomationGate?.gate ?? (automationQualityLoaded ? unavailableValue : loadingStatus),
gaps: formatAutomationNumber(recurrenceSummary?.automation_gap_group_total, automationBriefLoaded),
}),
detail: tDashboard('automationDiagrams.workspace.liveEvidence.playbook.detail', {
workItems: formatAutomationNumber(recurrenceSummary?.open_work_item_group_total, automationBriefLoaded),
verifiedGroups: formatAutomationNumber(recurrenceSummary?.verified_repair_group_total, automationBriefLoaded),
linkedAutoRepair: formatAutomationNumber(recurrenceSummary?.auto_repair_linked_total, automationBriefLoaded),
}),
source: tDashboard('automationDiagrams.workspace.liveEvidence.sources.qualityAndRecurrence'),
updated: formatLiveEvidenceTime(recurrenceSummary?.latest_received_at),
},
},
{
key: 'ansible',
@@ -1051,6 +1327,19 @@ export default function Home({ params }: { params: { locale: string } }) {
owner: tDashboard('automationDiagrams.workspace.inspector.stages.ansible.owner'),
evidence: tDashboard('automationDiagrams.workspace.inspector.stages.ansible.evidence'),
nextAction: tDashboard('automationDiagrams.workspace.inspector.stages.ansible.nextAction'),
liveEvidence: {
metric: tDashboard('automationDiagrams.workspace.liveEvidence.ansible.metric', {
checkMode: formatAutomationNumber(executionBackend?.ansible_check_mode_total, automationQualityLoaded),
pending: formatAutomationNumber(executionBackend?.ansible_pending_check_mode_total, automationQualityLoaded),
}),
detail: tDashboard('automationDiagrams.workspace.liveEvidence.ansible.detail', {
blocker: ansibleRuntime?.blockers?.join(' / ') || (automationQualityLoaded ? unavailableValue : loadingStatus),
candidates: formatAutomationNumber(executionBackend?.ansible_candidate_total, automationQualityLoaded),
operations: formatAutomationNumber(executionBackend?.operation_records_total, automationQualityLoaded),
}),
source: tDashboard('automationDiagrams.workspace.liveEvidence.sources.truthChainQuality'),
updated: automationQualityLoaded ? tDashboard('automationDiagrams.workspace.liveEvidence.realtime') : loadingStatus,
},
},
{
key: 'approval',
@@ -1065,6 +1354,20 @@ export default function Home({ params }: { params: { locale: string } }) {
owner: tDashboard('automationDiagrams.workspace.inspector.stages.approval.owner'),
evidence: tDashboard('automationDiagrams.workspace.inspector.stages.approval.evidence'),
nextAction: tDashboard('automationDiagrams.workspace.inspector.stages.approval.nextAction'),
liveEvidence: {
metric: tDashboard('automationDiagrams.workspace.liveEvidence.approval.metric', {
pending: formatAutomationNumber(pendingApprovals, true),
verified: formatAutomationNumber(verifiedAutomationTotal, automationQualityLoaded),
evaluated: formatAutomationNumber(evaluatedAutomationTotal, automationQualityLoaded),
}),
detail: tDashboard('automationDiagrams.workspace.liveEvidence.approval.detail', {
humanGates: formatAutomationNumber(runEvidenceSummary.humanGateRuns, automationBriefLoaded),
autoRepairRecords: formatAutomationNumber(statusChainEvidenceSummary.autoRepairRecords, true),
operations: formatAutomationNumber(statusChainEvidenceSummary.operationRecords, true),
}),
source: tDashboard('automationDiagrams.workspace.liveEvidence.sources.approvalsAndQuality'),
updated: automationQualityLoaded ? tDashboard('automationDiagrams.workspace.liveEvidence.realtime') : loadingStatus,
},
},
{
key: 'verify',
@@ -1079,6 +1382,19 @@ export default function Home({ params }: { params: { locale: string } }) {
owner: tDashboard('automationDiagrams.workspace.inspector.stages.verify.owner'),
evidence: tDashboard('automationDiagrams.workspace.inspector.stages.verify.evidence'),
nextAction: tDashboard('automationDiagrams.workspace.inspector.stages.verify.nextAction'),
liveEvidence: {
metric: tDashboard('automationDiagrams.workspace.liveEvidence.verify.metric', {
stale: formatAutomationNumber(kmBurndownSnapshot?.stale_count ?? kmStaleTotal, automationBriefLoaded),
ratio: staleRatioLabel,
}),
detail: tDashboard('automationDiagrams.workspace.liveEvidence.verify.detail', {
pending: formatAutomationNumber(kmOwnerReviewBurndown?.pending_owner_reviews, automationBriefLoaded),
completed: formatAutomationNumber(kmOwnerReviewBurndown?.completed_owner_reviews, automationBriefLoaded),
remaining: formatAutomationNumber(kmOwnerReviewBurndown?.entries_to_threshold, automationBriefLoaded),
}),
source: tDashboard('automationDiagrams.workspace.liveEvidence.sources.kmBurndown'),
updated: formatLiveEvidenceTime(kmOwnerReviewBurndown?.generated_at),
},
},
]
const runtimeTopologyLayers = [
@@ -1714,6 +2030,18 @@ export default function Home({ params }: { params: { locale: string } }) {
<div style={{ marginTop: 5, fontSize: 10, color: '#5f5b52', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{stage.detail}
</div>
<div style={{
marginTop: 7,
borderTop: `0.5px solid ${tone.border}`,
paddingTop: 6,
fontSize: 9,
fontWeight: 800,
color: tone.color,
lineHeight: 1.35,
overflowWrap: 'anywhere',
}}>
{stage.liveEvidence.metric}
</div>
</a>
)
})}
@@ -1806,6 +2134,56 @@ export default function Home({ params }: { params: { locale: string } }) {
</div>
))}
</div>
<div style={{
gridColumn: compactViewport ? 'auto' : '1 / -1',
border: `0.5px solid ${selectedBlueprintTone.border}`,
borderRadius: 8,
background: '#fff',
padding: '9px 10px',
}}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
<div style={{ fontSize: 10, fontWeight: 800, color: '#77736a', textTransform: 'uppercase', letterSpacing: 0.5 }}>
{tDashboard('automationDiagrams.workspace.liveEvidence.title')}
</div>
<span style={{
border: `0.5px solid ${selectedBlueprintTone.border}`,
background: selectedBlueprintTone.bg,
color: selectedBlueprintTone.color,
borderRadius: 999,
padding: '2px 7px',
fontSize: 9,
fontWeight: 800,
}}>
{selectedBlueprintStage.liveEvidence.updated}
</span>
</div>
<div style={{
marginTop: 8,
display: 'grid',
gridTemplateColumns: compactViewport ? '1fr' : 'repeat(auto-fit, minmax(170px, 1fr))',
gap: 8,
}}>
{[
[tDashboard('automationDiagrams.workspace.liveEvidence.fields.metric'), selectedBlueprintStage.liveEvidence.metric],
[tDashboard('automationDiagrams.workspace.liveEvidence.fields.detail'), selectedBlueprintStage.liveEvidence.detail],
[tDashboard('automationDiagrams.workspace.liveEvidence.fields.source'), selectedBlueprintStage.liveEvidence.source],
].map(([label, value]) => (
<div key={label} style={{
border: '0.5px solid #e0ddd4',
borderRadius: 7,
background: '#fbfaf6',
padding: '8px 9px',
}}>
<div style={{ fontSize: 9, fontWeight: 800, color: '#77736a', textTransform: 'uppercase', letterSpacing: 0.5 }}>
{label}
</div>
<div style={{ marginTop: 5, fontSize: 11, fontWeight: 700, color: '#2e2b26', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{value}
</div>
</div>
))}
</div>
</div>
</div>
</div>