feat(governance): 新增全產品 Code Review 防木馬 Gate
Some checks failed
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / tests (push) Successful in 1m49s
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-19 00:26:04 +08:00
parent 9ebab2db6e
commit 4a14860c60
9 changed files with 1496 additions and 0 deletions

View File

@@ -3,6 +3,7 @@
import { AppLayout } from '@/components/layout'
import { IwoooSReadOnlyBridge } from '@/components/security/iwooos-read-only-bridge'
import { publicBoundaryText } from '@/lib/public-security-redaction'
import { useEffect, useMemo, useState } from 'react'
import {
Activity,
ArrowRight,
@@ -13,15 +14,92 @@ import {
ExternalLink,
FileCheck2,
GitBranch,
Globe2,
Gauge,
ListChecks,
LockKeyhole,
PackageSearch,
SearchCheck,
ShieldAlert,
ShieldCheck,
Workflow,
} from 'lucide-react'
import { useTranslations } from 'next-intl'
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
type ProductCodeReviewGateSnapshot = {
schema_version: string
generated_at: string
program_status: {
overall_completion_percent: number
current_task_id: string
next_task_id: string
read_only_mode: boolean
}
rollups: {
product_scope_count: number
public_route_count_minimum: number
source_candidate_repo_count: number
pre_deploy_gate_count: number
post_deploy_gate_count: number
ai_reviewer_count: number
mainstream_tool_count: number
owner_review_required_count: number
critical_gap_count: number
active_write_gate_count: number
action_button_count: number
}
tenant_asset_inventory_summary: {
public_route_count?: number
source_candidate_repo_count?: number
}
product_review_matrix: Array<{
product_id: string
product_name: string
category: string
owner_lane: string
coverage_status: string
public_route_count: number
source_repo_count: number
gate_state: string
runtime_gate_count: number
action_button_count: number
}>
pre_deploy_gates: Array<{
gate_id: string
label: string
status: string
owner_agent: string
current_gap: string
next_action: string
}>
post_deploy_gates: Array<{
gate_id: string
label: string
status: string
owner_agent: string
current_gap: string
next_action: string
}>
ai_reviewer_lanes: Array<{
agent_id: string
label: string
role: string
allowed_output: string
write_allowed: boolean
}>
mainstream_tool_lanes: Array<{
tool_id: string
label: string
category: string
integration_status: string
recommended_role: string
blocked_now: string[]
}>
gate_boundaries: Record<string, boolean>
}
const agents = [
{ name: 'Hermes', roleKey: 'hermes', state: 'wired' },
{ name: 'OpenClaw', roleKey: 'openclaw', state: 'wired' },
@@ -89,9 +167,50 @@ const summaryCards = [
{ key: 'report', icon: Gauge, iconClassName: 'text-lime-300' },
]
async function fetchProductCodeReviewGate(): Promise<ProductCodeReviewGateSnapshot | null> {
if (!API_BASE) return null
const response = await fetch(`${API_BASE}/api/v1/agents/product-code-review-gate`, {
cache: 'no-store',
})
if (!response.ok) return null
return response.json()
}
export default function CodeReviewPage({ params }: { params: { locale: string } }) {
const t = useTranslations('codeReview')
const [gateSnapshot, setGateSnapshot] = useState<ProductCodeReviewGateSnapshot | null>(null)
const [gateLoaded, setGateLoaded] = useState(false)
const evidenceHref = `/${params.locale}/awooop/runs`
const products = useMemo(
() => gateSnapshot?.product_review_matrix ?? [],
[gateSnapshot]
)
const preDeployGates = useMemo(
() => gateSnapshot?.pre_deploy_gates.slice(0, 4) ?? [],
[gateSnapshot]
)
const postDeployGates = useMemo(
() => gateSnapshot?.post_deploy_gates.slice(0, 4) ?? [],
[gateSnapshot]
)
const toolLanes = useMemo(
() => gateSnapshot?.mainstream_tool_lanes ?? [],
[gateSnapshot]
)
useEffect(() => {
let cancelled = false
fetchProductCodeReviewGate()
.then((payload) => {
if (!cancelled) setGateSnapshot(payload)
})
.finally(() => {
if (!cancelled) setGateLoaded(true)
})
return () => {
cancelled = true
}
}, [])
return (
<AppLayout locale={params.locale}>
@@ -117,6 +236,209 @@ export default function CodeReviewPage({ params }: { params: { locale: string }
<IwoooSReadOnlyBridge variant="dark" />
<section
id="product-code-review-gate"
data-testid="product-code-review-gate"
className="grid min-w-0 gap-4 rounded border border-gray-800 bg-gray-950 p-4"
>
<div className="grid gap-3 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-start">
<div className="min-w-0">
<div className="flex items-center gap-2 text-xs font-mono uppercase text-amber-300">
<ShieldAlert className="h-4 w-4" />
{t('gateBoard.eyebrow')}
</div>
<h2 className="mt-2 text-xl font-semibold text-white">{t('gateBoard.title')}</h2>
<p className="mt-2 max-w-3xl text-sm leading-6 text-gray-400">{t('gateBoard.subtitle')}</p>
</div>
<div className="grid grid-cols-2 gap-2 text-xs font-mono text-gray-400 sm:grid-cols-4 lg:min-w-[30rem]">
<div className="rounded border border-gray-800 bg-black/25 px-3 py-2">
<div className="text-gray-500">{t('gateBoard.metrics.current')}</div>
<div className="mt-1 text-lg text-white">{gateSnapshot?.program_status.current_task_id ?? '--'}</div>
</div>
<div className="rounded border border-gray-800 bg-black/25 px-3 py-2">
<div className="text-gray-500">{t('gateBoard.metrics.next')}</div>
<div className="mt-1 text-lg text-white">{gateSnapshot?.program_status.next_task_id ?? '--'}</div>
</div>
<div className="rounded border border-gray-800 bg-black/25 px-3 py-2">
<div className="text-gray-500">{t('gateBoard.metrics.writeGate')}</div>
<div className="mt-1 text-lg text-emerald-200">{gateSnapshot?.rollups.active_write_gate_count ?? 0}</div>
</div>
<div className="rounded border border-gray-800 bg-black/25 px-3 py-2">
<div className="text-gray-500">{t('gateBoard.metrics.actions')}</div>
<div className="mt-1 text-lg text-emerald-200">{gateSnapshot?.rollups.action_button_count ?? 0}</div>
</div>
</div>
</div>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-6">
{[
{
key: 'products',
value: gateSnapshot?.rollups.product_scope_count ?? products.length,
icon: Globe2,
},
{
key: 'routes',
value: gateSnapshot?.tenant_asset_inventory_summary.public_route_count ?? gateSnapshot?.rollups.public_route_count_minimum ?? 0,
icon: GitBranch,
},
{
key: 'pre',
value: gateSnapshot?.rollups.pre_deploy_gate_count ?? 0,
icon: ShieldCheck,
},
{
key: 'post',
value: gateSnapshot?.rollups.post_deploy_gate_count ?? 0,
icon: CheckCircle2,
},
{
key: 'reviewers',
value: gateSnapshot?.rollups.ai_reviewer_count ?? 0,
icon: Bot,
},
{
key: 'tools',
value: gateSnapshot?.rollups.mainstream_tool_count ?? 0,
icon: PackageSearch,
},
].map((metric) => {
const Icon = metric.icon
return (
<div key={metric.key} className="rounded border border-gray-800 bg-black/25 p-3">
<div className="flex items-center gap-2 text-xs text-gray-500">
<Icon className="h-4 w-4 text-sky-300" />
{t(`gateBoard.metricCards.${metric.key}.label` as never)}
</div>
<div className="mt-3 text-2xl font-semibold text-white">{metric.value}</div>
<div className="mt-1 text-xs leading-5 text-gray-500">{t(`gateBoard.metricCards.${metric.key}.detail` as never)}</div>
</div>
)
})}
</div>
<div className="grid gap-3 lg:grid-cols-5">
{['assets', 'preDeploy', 'aiReview', 'postDeploy', 'ownerGate'].map((step, index) => (
<div key={step} className="min-w-0 rounded border border-gray-800 bg-black/20 px-3 py-3">
<div className="font-mono text-xs text-gray-500">{String(index + 1).padStart(2, '0')}</div>
<div className="mt-2 text-sm font-semibold text-white">{t(`gateBoard.flow.${step}.title` as never)}</div>
<div className="mt-1 text-xs leading-5 text-gray-400">{t(`gateBoard.flow.${step}.detail` as never)}</div>
</div>
))}
</div>
{!gateLoaded ? (
<div className="rounded border border-gray-800 bg-black/20 px-3 py-2 text-sm text-gray-500">
{t('gateBoard.loading')}
</div>
) : gateSnapshot ? null : (
<div className="rounded border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm text-amber-100">
{t('gateBoard.unavailable')}
</div>
)}
</section>
<section
data-testid="product-code-review-matrix"
className="grid min-w-0 gap-4 rounded border border-gray-800 bg-gray-950 p-4"
>
<div className="grid gap-2 md:grid-cols-[minmax(0,1fr)_auto] md:items-center">
<div>
<div className="flex items-center gap-2 text-sm font-semibold text-white">
<Globe2 className="h-4 w-4 text-sky-300" />
{t('products.title')}
</div>
<div className="mt-1 text-xs text-gray-500">{t('products.subtitle')}</div>
</div>
<div className="rounded border border-emerald-500/30 px-3 py-1 text-xs font-mono text-emerald-200">
{publicBoundaryText('runtime_execution_authorized=false')}
</div>
</div>
<div className="grid gap-2 md:grid-cols-2 xl:grid-cols-4">
{products.map((product) => (
<div key={product.product_id} className="min-w-0 rounded border border-gray-800 bg-black/20 p-3">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<div className="font-mono text-xs text-gray-500">{product.product_id}</div>
<div className="mt-1 break-words text-sm font-semibold text-white">{product.product_name}</div>
</div>
<span className="shrink-0 rounded bg-sky-500/10 px-2 py-1 text-xs font-mono text-sky-200">
{product.gate_state === 'owner_review_required'
? t('products.states.ownerReview')
: product.gate_state === 'source_mapping_required'
? t('products.states.sourceMapping')
: t('products.states.visible')}
</span>
</div>
<div className="mt-3 grid grid-cols-3 gap-2 text-xs">
<div className="rounded border border-gray-800 px-2 py-2">
<div className="text-gray-500">{t('products.labels.routes')}</div>
<div className="mt-1 font-mono text-white">{product.public_route_count}</div>
</div>
<div className="rounded border border-gray-800 px-2 py-2">
<div className="text-gray-500">{t('products.labels.sources')}</div>
<div className="mt-1 font-mono text-white">{product.source_repo_count}</div>
</div>
<div className="rounded border border-gray-800 px-2 py-2">
<div className="text-gray-500">{t('products.labels.gates')}</div>
<div className="mt-1 font-mono text-emerald-200">{product.runtime_gate_count}</div>
</div>
</div>
<div className="mt-3 break-words text-xs leading-5 text-gray-500">
{product.owner_lane} · {product.coverage_status}
</div>
</div>
))}
</div>
</section>
<section className="grid gap-4 lg:grid-cols-2">
<div data-testid="product-code-review-prepost-gates" className="rounded border border-gray-800 bg-gray-950 p-4">
<div className="flex items-center gap-2 text-sm font-semibold text-white">
<ShieldCheck className="h-4 w-4 text-emerald-300" />
{t('prepost.title')}
</div>
<div className="mt-3 grid gap-3 md:grid-cols-2">
<div className="space-y-2">
<div className="text-xs font-mono uppercase text-gray-500">{t('prepost.pre')}</div>
{preDeployGates.map((gate) => (
<div key={gate.gate_id} className="rounded border border-gray-800 bg-black/20 px-3 py-2">
<div className="text-sm font-semibold text-white">{gate.label}</div>
<div className="mt-1 text-xs text-gray-500">{gate.owner_agent} · {gate.status}</div>
</div>
))}
</div>
<div className="space-y-2">
<div className="text-xs font-mono uppercase text-gray-500">{t('prepost.post')}</div>
{postDeployGates.map((gate) => (
<div key={gate.gate_id} className="rounded border border-gray-800 bg-black/20 px-3 py-2">
<div className="text-sm font-semibold text-white">{gate.label}</div>
<div className="mt-1 text-xs text-gray-500">{gate.owner_agent} · {gate.status}</div>
</div>
))}
</div>
</div>
</div>
<div data-testid="product-code-review-tool-lanes" className="rounded border border-gray-800 bg-gray-950 p-4">
<div className="flex items-center gap-2 text-sm font-semibold text-white">
<PackageSearch className="h-4 w-4 text-amber-300" />
{t('tools.title')}
</div>
<div className="mt-3 grid gap-2 sm:grid-cols-2">
{toolLanes.map((tool) => (
<div key={tool.tool_id} className="rounded border border-gray-800 bg-black/20 px-3 py-2">
<div className="flex items-center justify-between gap-3">
<div className="font-semibold text-white">{tool.label}</div>
<div className="font-mono text-xs text-amber-200">{t('tools.candidate')}</div>
</div>
<div className="mt-1 text-xs leading-5 text-gray-500">{tool.category} · {tool.integration_status}</div>
</div>
))}
</div>
</div>
</section>
<section
id="code-review-codex-handoff-board"
data-testid="code-review-codex-handoff-board"