feat(awooop): expose controlled execution preflight
All checks were successful
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / tests (push) Successful in 1m42s
CD Pipeline / build-and-deploy (push) Successful in 5m6s
CD Pipeline / post-deploy-checks (push) Successful in 1m28s

This commit is contained in:
Your Name
2026-06-26 07:50:53 +08:00
parent 63545353dc
commit 7c220fd083
5 changed files with 389 additions and 6 deletions

View File

@@ -1,6 +1,6 @@
"use client";
import { Activity, BookOpenCheck, CheckCircle2, Link2, RadioTower, Route, ShieldAlert, TriangleAlert, Wrench } from "lucide-react";
import { Activity, BookOpenCheck, CheckCircle2, Link2, RadioTower, Route, ShieldAlert, ShieldCheck, TriangleAlert, Wrench } from "lucide-react";
import { useTranslations } from "next-intl";
import { Link } from "@/i18n/routing";
@@ -235,6 +235,38 @@ export interface AwoooPStatusChain {
next_step?: string | null;
runtime_write_allowed?: boolean | null;
}>;
controlled_execution_preflight?: {
schema_version?: string | null;
status?: string | null;
source_id?: string | null;
work_item_id?: string | null;
runtime_execution_authorized?: boolean | null;
runtime_write_allowed?: boolean | null;
allowed_route_count?: number | null;
candidate_route_count?: number | null;
ready_count?: number | null;
total_count?: number | null;
blocked_count?: number | null;
next_action?: string | null;
blocked_reason?: string | null;
routes?: Array<{
route_id?: string | null;
transport?: string | null;
status?: string | null;
source_asset_id?: string | null;
check_mode_playbook_path?: string | null;
apply_playbook_path?: string | null;
allowed?: boolean | null;
blocker?: string | null;
}>;
prerequisites?: Array<{
key?: string | null;
status?: string | null;
detail?: string | null;
required?: boolean | null;
}>;
forbidden_until_released?: string[];
};
};
candidate?: {
catalog_id?: string | null;
@@ -543,6 +575,10 @@ export function AwoooPStatusChainPanel({
const closureOwnerPackage = closureReadiness?.owner_release_package;
const closureVerifierPackage = closureReadiness?.release_verifier_package;
const closureTasks = closureReadiness?.closure_tasks ?? [];
const controlledExecutionPreflight = closureReadiness?.controlled_execution_preflight;
const controlledExecutionRoutes = controlledExecutionPreflight?.routes ?? [];
const controlledExecutionPrerequisites =
controlledExecutionPreflight?.prerequisites ?? [];
const ownerReviewChecklist = automationHandoff?.owner_review_checklist ?? [];
const forbiddenActions = automationHandoff?.forbidden_actions ?? [];
const sourceToolchainTone: SourceFlowTone = sourceCorrelation
@@ -688,6 +724,14 @@ export function AwoooPStatusChainPanel({
blocked_before_runtime_gate: t("applyGate.closureStatuses.blockedBeforeRuntimeGate"),
blocked_until_verifier_passes: t("applyGate.closureStatuses.blockedUntilVerifierPasses"),
snapshot_unavailable: t("applyGate.closureStatuses.snapshotUnavailable"),
candidate_ready_no_runtime_authority: t("applyGate.closureStatuses.candidateReadyNoRuntimeAuthority"),
route_missing: t("applyGate.closureStatuses.routeMissing"),
blocked_missing_dry_run: t("applyGate.closureStatuses.blockedMissingDryRun"),
blocked_missing_owner_release: t("applyGate.closureStatuses.blockedMissingOwnerRelease"),
blocked_missing_maintenance_window: t("applyGate.closureStatuses.blockedMissingMaintenanceWindow"),
blocked_missing_rollback_owner: t("applyGate.closureStatuses.blockedMissingRollbackOwner"),
blocked_missing_post_apply_verifier: t("applyGate.closureStatuses.blockedMissingPostApplyVerifier"),
blocked_until_verified_execution: t("applyGate.closureStatuses.blockedUntilVerifiedExecution"),
};
return labels[String(status ?? "")] ?? handoffStatusLabel(status);
};
@@ -701,6 +745,18 @@ export function AwoooPStatusChainPanel({
};
return labels[String(key ?? "")] ?? valueOrEmpty(key, emptyLabel);
};
const controlledExecutionPrerequisiteLabel = (key: string | null | undefined) => {
const labels: Record<string, string> = {
dry_run_passed: t("applyGate.controlledPrerequisites.dryRunPassed"),
allowlisted_route_candidate: t("applyGate.controlledPrerequisites.allowlistedRouteCandidate"),
owner_release_receipt: t("applyGate.controlledPrerequisites.ownerReleaseReceipt"),
maintenance_window: t("applyGate.controlledPrerequisites.maintenanceWindow"),
rollback_owner: t("applyGate.controlledPrerequisites.rollbackOwner"),
post_apply_verifier: t("applyGate.controlledPrerequisites.postApplyVerifier"),
km_playbook_writeback: t("applyGate.controlledPrerequisites.kmPlaybookWriteback"),
};
return labels[String(key ?? "")] ?? valueOrEmpty(key, emptyLabel);
};
const handoffWorkItemHref = automationHandoff?.work_item_id
? `/awooop/work-items?project_id=awoooi&work_item_id=${encodeURIComponent(automationHandoff.work_item_id)}${automationHandoff.source_id ? `&incident_id=${encodeURIComponent(automationHandoff.source_id)}` : ""}`
: null;
@@ -722,9 +778,10 @@ export function AwoooPStatusChainPanel({
return labels[String(status ?? "")] ?? valueOrEmpty(status, emptyLabel);
};
const handoffTone = (status: string | null | undefined): SourceFlowTone => {
if (status === "passed") return "success";
if (status === "blocked") return "blocked";
if (status === "warning") return "warning";
const normalized = String(status ?? "");
if (normalized === "passed") return "success";
if (normalized === "warning" || normalized.includes("candidate_ready")) return "warning";
if (normalized === "blocked" || normalized.startsWith("blocked") || normalized === "route_missing") return "blocked";
return "neutral";
};
const drilldownItems = [
@@ -1123,6 +1180,109 @@ export function AwoooPStatusChainPanel({
</div>
))}
</div>
{controlledExecutionPreflight ? (
<>
<div className="border-t border-[#e0ddd4] bg-[#fbfaf5] px-4 py-2">
<div className="flex flex-wrap items-center justify-between gap-2">
<div className="min-w-0">
<p className="text-xs font-semibold text-[#141413]">{t("applyGate.controlledExecutionPreflightTitle")}</p>
<p className="mt-1 truncate font-mono text-[11px] text-[#77736a]" title={valueOrEmpty(controlledExecutionPreflight.blocked_reason, emptyLabel)}>
{valueOrEmpty(controlledExecutionPreflight.blocked_reason, emptyLabel)}
</p>
</div>
<span className="border border-[#e2a29b] bg-[#fff0ef] px-2 py-0.5 font-mono text-[11px] font-semibold text-[#9f2f25]">
{closureStatusLabel(controlledExecutionPreflight.status)}
</span>
</div>
</div>
<div className="grid gap-px bg-[#e0ddd4] md:grid-cols-4">
{[
{
key: "candidate",
label: t("applyGate.controlledCandidateRoutes"),
value: controlledExecutionPreflight.candidate_route_count ?? 0,
},
{
key: "allowed",
label: t("applyGate.controlledAllowedRoutes"),
value: controlledExecutionPreflight.allowed_route_count ?? 0,
},
{
key: "blocked",
label: t("applyGate.controlledBlockedCount"),
value: controlledExecutionPreflight.blocked_count ?? 0,
},
{
key: "runtime",
label: t("applyGate.runtimeWrite", {
value: boolValue(controlledExecutionPreflight.runtime_write_allowed, emptyLabel),
}),
value: boolValue(controlledExecutionPreflight.runtime_execution_authorized, emptyLabel),
},
].map((item) => (
<div key={item.key} className="min-w-0 bg-white px-4 py-3">
<p className="text-xs font-semibold text-[#77736a]">{item.label}</p>
<p className="mt-1 truncate font-mono text-lg font-semibold text-[#141413]" title={String(item.value)}>
{item.value}
</p>
</div>
))}
</div>
<div className="grid gap-px bg-[#e0ddd4] lg:grid-cols-7">
{(controlledExecutionPrerequisites.length ? controlledExecutionPrerequisites : [{ key: emptyLabel, status: emptyLabel, detail: emptyLabel, required: true }]).map((item, index) => (
<div key={`${item.key}-${index}`} className="min-w-0 bg-white px-4 py-3">
<div className="flex min-w-0 items-start gap-3">
<span className={cn(
"flex h-8 w-8 shrink-0 items-center justify-center border",
sourceFlowToneClass(handoffTone(item.status))
)}>
<ShieldCheck className="h-4 w-4" aria-hidden="true" />
</span>
<div className="min-w-0">
<p className="text-xs font-semibold text-[#77736a]">{controlledExecutionPrerequisiteLabel(item.key)}</p>
<p className="mt-1 truncate font-mono text-sm font-semibold text-[#141413]" title={closureStatusLabel(item.status)}>
{closureStatusLabel(item.status)}
</p>
</div>
</div>
<p className="mt-2 truncate font-mono text-xs text-[#5f5b52]" title={valueOrEmpty(item.detail, emptyLabel)}>
{valueOrEmpty(item.detail, emptyLabel)}
</p>
</div>
))}
</div>
<div className="grid gap-px bg-[#e0ddd4]">
{(controlledExecutionRoutes.length ? controlledExecutionRoutes : [{ route_id: emptyLabel, transport: emptyLabel, status: emptyLabel, source_asset_id: emptyLabel, check_mode_playbook_path: emptyLabel, apply_playbook_path: emptyLabel, allowed: false, blocker: emptyLabel }]).map((route, index) => (
<div key={`${route.route_id}-${index}`} className="min-w-0 bg-white px-4 py-3">
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="min-w-0">
<p className="text-xs font-semibold text-[#77736a]">{t("applyGate.controlledRouteTitle")}</p>
<p className="mt-1 break-all font-mono text-sm font-semibold text-[#141413]" title={valueOrEmpty(route.route_id, emptyLabel)}>
{valueOrEmpty(route.route_id, emptyLabel)}
</p>
</div>
<span className="shrink-0 border border-[#e2a29b] bg-[#fff0ef] px-2 py-0.5 font-mono text-[11px] text-[#9f2f25]">
{t("applyGate.controlledAllowed", {
value: boolValue(route.allowed, emptyLabel),
})}
</span>
</div>
<div className="mt-3 grid gap-2 lg:grid-cols-3">
<span className="min-w-0 border border-[#ece8dd] bg-[#faf9f3] px-2 py-1 font-mono text-[11px] text-[#141413]">
{valueOrEmpty(route.transport, emptyLabel)} · {closureStatusLabel(route.status)}
</span>
<span className="min-w-0 break-all border border-[#ece8dd] bg-[#faf9f3] px-2 py-1 font-mono text-[11px] text-[#141413]">
{valueOrEmpty(route.apply_playbook_path, emptyLabel)}
</span>
<span className="min-w-0 break-all border border-[#ece8dd] bg-[#faf9f3] px-2 py-1 font-mono text-[11px] text-[#141413]">
{valueOrEmpty(route.blocker, emptyLabel)}
</span>
</div>
</div>
))}
</div>
</>
) : null}
<div className="grid gap-px bg-[#e0ddd4] lg:grid-cols-2">
<div className="min-w-0 bg-white px-4 py-3">
<div className="flex items-center justify-between gap-3">