feat(web): show GitHub readiness in AwoooP runs

This commit is contained in:
Your Name
2026-05-21 10:21:08 +08:00
parent 77e9f45db7
commit 6e1dbfe964
8 changed files with 355 additions and 3 deletions

View File

@@ -4018,6 +4018,44 @@
"ownerResponse": "Owner response received / accepted remain 0, so any further Run movement waits for human intake.",
"activeGates": "Active runtime gates remain 0. Do not open gates or create action buttons from Run Monitor."
}
},
"githubRunReadinessBoundary": {
"title": "GitHub Primary Readiness Run Boundary",
"subtitle": "Run Monitor mirrors that GitHub primary readiness still cannot create a security run. This is not platform run, execution router, repo creation, or primary switch authorization.",
"badge": "Run boundary",
"openIwooos": "Open IwoooS",
"runRefsTitle": "GitHub Readiness Run Refs",
"boundaryLabel": "GitHub / Run Boundary",
"boundaryTitle": "No GitHub primary run is executable",
"boundaryDetail": "This panel only projects source-control readiness into Run Monitor language. It does not create platform runs, connect an execution router, create GitHub repos, mutate refs, change workflows / secrets, collect secret values, switch primary, or disable Gitea.",
"metrics": {
"candidateRepos": {
"label": "Candidate Repos",
"detail": "8 candidate repos are readiness visibility only and do not create GitHub repo creation runs."
},
"inScopeRepos": {
"label": "In-scope Repos",
"detail": "7 repos still wait for owner response. Run Monitor must not open tasks for them."
},
"securityRuns": {
"label": "GitHub Security Runs",
"detail": "Currently 0. Readiness visibility is not platform run creation."
},
"ownerResponses": {
"label": "Owner Responses",
"detail": "22 response templates remain 0 received / 0 accepted."
},
"workflowInventory": {
"label": "Workflow Inventory",
"detail": "Workflow / secret name inventory is still incomplete for 7 repos."
}
},
"runRefs": {
"primaryReadiness": "The primary readiness gate still reports ready=0 and cannot become a GitHub primary run.",
"ownerValidation": "The owner response validation rollup is still 0/22 and must not be autofilled as accepted by Run Monitor.",
"workflowInventory": "Workflow / secret name inventory only collects names and routing. It does not collect secret values or modify GitHub secrets.",
"rollbackAdr": "The rollback ADR has no owner-approved dry-run, so no primary switch run can start."
}
}
},
"contracts": {

View File

@@ -4019,6 +4019,44 @@
"ownerResponse": "owner response received / accepted 仍為 0任何 Run 進一步行動都要等待人工收件。",
"activeGates": "active runtime gates 仍為 0不從 Run 監控頁開 gate 或建立 action button。"
}
},
"githubRunReadinessBoundary": {
"title": "GitHub Primary Readiness Run 邊界",
"subtitle": "Run 監控同步顯示 GitHub primary readiness 仍不可產生 security run這不是 platform run、execution router、repo 建立或 primary 切換授權。",
"badge": "Run 邊界",
"openIwooos": "開啟 IwoooS",
"runRefsTitle": "GitHub readiness run refs",
"boundaryLabel": "GitHub / Run 邊界",
"boundaryTitle": "目前沒有 GitHub primary run 可執行",
"boundaryDetail": "這個面板只把 source-control readiness 投影到 Run 監控語境;不建立 platform run、不接 execution router、不建立 GitHub repo、不改 refs、不改 workflow / secrets、不收 secret value、不切 primary也不停用 Gitea。",
"metrics": {
"candidateRepos": {
"label": "Candidate repos",
"detail": "8 個候選 repo 只顯示 readiness不會產生 GitHub repo creation run。"
},
"inScopeRepos": {
"label": "In-scope repos",
"detail": "7 個 repo 仍等待 owner responseRun 監控不得替它們開任務。"
},
"securityRuns": {
"label": "GitHub security runs",
"detail": "目前為 0readiness visibility 不等於 platform run created。"
},
"ownerResponses": {
"label": "Owner responses",
"detail": "22 個 response templates 仍為 0 received / 0 accepted。"
},
"workflowInventory": {
"label": "Workflow inventory",
"detail": "7 個 repo 的 workflow / secret name inventory 仍未完成。"
}
},
"runRefs": {
"primaryReadiness": "Primary readiness gate 仍顯示 ready=0不能轉成 GitHub primary run。",
"ownerValidation": "Owner response validation rollup 仍是 0/22不能被 Run 監控自動補成 accepted。",
"workflowInventory": "Workflow / secret name inventory 仍只收集名稱與路由,不收 secret value、不修改 GitHub secrets。",
"rollbackAdr": "Rollback ADR 尚未完成 owner-approved dry-run因此不能啟動 primary switch run。"
}
}
},
"contracts": {

View File

@@ -19,7 +19,9 @@ import {
ChevronLeft,
ChevronRight,
Cpu,
GitBranch,
ListChecks,
Lock,
SearchCheck,
ShieldCheck,
TriangleAlert,
@@ -110,6 +112,19 @@ interface RecentEventsResponse {
limit: number;
}
type GitHubRunReadinessMetric = {
key: string;
value: string;
icon: typeof Activity;
tone: "neutral" | "warn";
};
type GitHubRunReadinessRef = {
key: string;
name: string;
status: string;
};
// =============================================================================
// 常數
// =============================================================================
@@ -269,6 +284,51 @@ const REMEDIATION_FILTER_OPTIONS: RemediationStatus[] = [
"no_evidence",
];
const githubRunReadinessMetrics: GitHubRunReadinessMetric[] = [
{ key: "candidateRepos", value: "8", icon: GitBranch, tone: "neutral" },
{ key: "inScopeRepos", value: "7", icon: ListChecks, tone: "warn" },
{ key: "securityRuns", value: "0", icon: Activity, tone: "warn" },
{ key: "ownerResponses", value: "0/22", icon: ShieldCheck, tone: "warn" },
{ key: "workflowInventory", value: "0/7", icon: SearchCheck, tone: "warn" },
];
const githubRunReadinessRefs: GitHubRunReadinessRef[] = [
{
key: "primaryReadiness",
name: "source_control_primary_readiness_gate_v1",
status: "ready=0",
},
{
key: "ownerValidation",
name: "source_control_owner_response_validation_rollup_v1",
status: "0/22",
},
{
key: "workflowInventory",
name: "source_control_workflow_secret_name_inventory_v1",
status: "0/7",
},
{
key: "rollbackAdr",
name: "source_control_primary_rollback_adr_v1",
status: "0/7",
},
];
const githubRunReadinessBoundaries = [
"security_run_created=false",
"github_primary_run_created=false",
"execution_router_linked=false",
"repo_creation_authorized=false",
"refs_mutation_authorized=false",
"workflow_secret_modification_authorized=false",
"secret_value_collection_allowed=false",
"github_primary_switch_authorized=false",
"gitea_disablement_authorized=false",
"runtime_execution_authorized=false",
"action_buttons_allowed=false",
];
function getRunLane(state: RunState): RunLane {
if (state === "pending") return "intake";
if (state === "waiting_tool") return "diagnosis";
@@ -703,6 +763,111 @@ function SecurityRunStateCandidatePanel() {
);
}
function GitHubRunReadinessBoundaryPanel() {
const t = useTranslations("awooop.runs.githubRunReadinessBoundary");
return (
<section className="overflow-hidden border border-[#e0ddd4] bg-white">
<div className="flex flex-wrap items-start justify-between gap-3 border-b border-[#e0ddd4] bg-[#faf9f3] px-4 py-3">
<div className="flex min-w-0 items-start gap-2">
<GitBranch className="mt-0.5 h-4 w-4 shrink-0 text-[#d97757]" aria-hidden="true" />
<div className="min-w-0">
<h3 className="text-sm font-semibold text-[#141413]">{t("title")}</h3>
<p className="mt-1 max-w-3xl text-xs leading-5 text-[#77736a]">{t("subtitle")}</p>
</div>
</div>
<div className="flex flex-wrap items-center gap-2">
<span className="inline-flex border border-[#d9b36f] bg-[#fff7e8] px-2 py-0.5 text-xs font-semibold text-[#8a5a08]">
{t("badge")}
</span>
<Link
href="/iwooos"
className="inline-flex items-center gap-2 border border-[#d8d3c7] bg-white px-3 py-1.5 text-xs font-semibold text-[#141413] hover:border-[#d97757]"
>
{t("openIwooos")}
<ArrowRight className="h-3.5 w-3.5 text-[#77736a]" aria-hidden="true" />
</Link>
</div>
</div>
<div className="grid gap-px bg-[#e0ddd4] md:grid-cols-2 xl:grid-cols-5">
{githubRunReadinessMetrics.map((metric) => {
const Icon = metric.icon;
return (
<div key={metric.key} className="min-h-[122px] bg-white px-4 py-3">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<p className="text-xs font-semibold text-[#77736a]">
{t(`metrics.${metric.key}.label` as never)}
</p>
<p className="mt-2 break-words font-mono text-2xl font-semibold text-[#141413]">
{metric.value}
</p>
</div>
<span
className={cn(
"flex h-8 w-8 shrink-0 items-center justify-center border",
metric.tone === "warn" && "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]",
metric.tone === "neutral" && "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]"
)}
>
<Icon className="h-4 w-4" aria-hidden="true" />
</span>
</div>
<p className="mt-3 text-xs leading-5 text-[#5f5b52]">
{t(`metrics.${metric.key}.detail` as never)}
</p>
</div>
);
})}
</div>
<div className="grid min-w-0 gap-4 p-4 xl:grid-cols-[1.1fr_0.9fr]">
<div className="min-w-0 border border-[#e0ddd4] bg-white">
<div className="border-b border-[#e0ddd4] bg-[#faf9f3] px-4 py-3 text-xs font-semibold text-[#141413]">
{t("runRefsTitle")}
</div>
<div className="divide-y divide-[#eee9dd]">
{githubRunReadinessRefs.map((ref) => (
<div key={ref.key} className="grid gap-3 px-4 py-3 md:grid-cols-[minmax(0,1fr)_96px]">
<div className="min-w-0">
<p className="break-all font-mono text-xs font-semibold text-[#141413]">
{ref.name}
</p>
<p className="mt-1 text-xs leading-5 text-[#5f5b52]">
{t(`runRefs.${ref.key}` as never)}
</p>
</div>
<div>
<span className="inline-flex border border-[#d9b36f] bg-[#fff7e8] px-2 py-0.5 font-mono text-xs font-semibold text-[#8a5a08]">
{ref.status}
</span>
</div>
</div>
))}
</div>
</div>
<div className="min-w-0 border border-[#e0ddd4] bg-[#faf9f3] px-4 py-3">
<div className="flex items-start gap-2">
<Lock className="mt-0.5 h-4 w-4 shrink-0 text-[#8a5a08]" aria-hidden="true" />
<div>
<p className="text-xs font-semibold text-[#77736a]">{t("boundaryLabel")}</p>
<p className="mt-2 text-sm font-semibold text-[#141413]">{t("boundaryTitle")}</p>
</div>
</div>
<p className="mt-2 text-xs leading-5 text-[#5f5b52]">{t("boundaryDetail")}</p>
<div className="mt-4 grid gap-2 break-all font-mono text-xs text-[#141413]">
{githubRunReadinessBoundaries.map((boundary) => (
<span key={boundary}>{boundary}</span>
))}
</div>
</div>
</div>
</section>
);
}
// =============================================================================
// Main Component
// =============================================================================
@@ -944,6 +1109,8 @@ export default function RunsPage() {
<SecurityRunStateCandidatePanel />
<GitHubRunReadinessBoundaryPanel />
<GroupedAlertEventsPanel events={groupedEvents} />
{/* Filters */}