feat(governance): preview stale km completion batches
All checks were successful
CD Pipeline / tests (push) Successful in 1m8s
Code Review / ai-code-review (push) Successful in 11s
Type Sync Check / check-type-sync (push) Successful in 26s
CD Pipeline / build-and-deploy (push) Successful in 4m9s
CD Pipeline / post-deploy-checks (push) Successful in 1m33s

This commit is contained in:
Your Name
2026-05-24 23:15:03 +08:00
parent 1a4ac330b1
commit 4cfc6a4c79
7 changed files with 624 additions and 7 deletions

View File

@@ -616,6 +616,10 @@ type KnowledgeStaleOwnerReviewCompletionQueueResponse = {
schema_version?: string;
project_id: string;
status_bucket: "all" | "ready" | "blocked" | "completed" | "failed" | "pending";
priority_tiers?: string[];
recommended_completion_outcome?: "all" | "refresh_with_evidence" | "archive" | "supersede";
batch_governance_event_id?: string | null;
can_preview?: boolean | null;
total: number;
returned: number;
pending_count: number;
@@ -630,6 +634,38 @@ type KnowledgeStaleOwnerReviewCompletionQueueResponse = {
generated_at?: string | null;
};
type KnowledgeStaleOwnerReviewCompletionBatchPreviewResponse = {
schema_version?: string;
project_id: string;
status: "dry_run";
owner: string;
owner_note?: string | null;
status_bucket: "all" | "ready" | "blocked" | "completed" | "failed" | "pending";
priority_tiers: string[];
recommended_completion_outcome: "all" | "refresh_with_evidence" | "archive" | "supersede";
batch_governance_event_id?: string | null;
requested_limit: number;
candidate_count: number;
previewable_count: number;
blocked_count: number;
completed_count: number;
failed_count: number;
writes_km: boolean;
writes_governance_audit: boolean;
batch_writes_allowed: boolean;
manual_review_required: boolean;
dry_run_plan_fingerprint: string;
next_action: string;
items: KnowledgeStaleOwnerReviewCompletionQueueItem[];
generated_at?: string | null;
};
type KnowledgeStaleOwnerReviewCompletionBatchPreviewAction = {
loading: boolean;
result: KnowledgeStaleOwnerReviewCompletionBatchPreviewResponse | null;
error: string | null;
};
type KnowledgeStaleOwnerReviewCompleteResponse = {
schema_version?: string;
entry_id: string;
@@ -2452,6 +2488,16 @@ function KnowledgeGovernancePanel({
});
const [staleReviewCompletionActions, setStaleReviewCompletionActions] =
useState<Record<string, KnowledgeStaleOwnerReviewCompleteAction>>({});
const [completionReadinessFilter, setCompletionReadinessFilter] =
useState<"all" | "ready" | "blocked" | "completed" | "failed" | "pending">("ready");
const [completionPriorityFilter, setCompletionPriorityFilter] =
useState<"all" | "P0" | "P1" | "P2">("all");
const [completionBatchPreviewAction, setCompletionBatchPreviewAction] =
useState<KnowledgeStaleOwnerReviewCompletionBatchPreviewAction>({
loading: false,
result: null,
error: null,
});
const items = queue?.items ?? [];
const draftGroups = groupKnowledgeReviewDrafts(reviewDrafts, items);
const dedupeGroups = dedupe?.groups ?? [];
@@ -2463,7 +2509,20 @@ function KnowledgeGovernancePanel({
const ownerReviewItems = ownerReviewInbox?.items ?? [];
const burnDownItems = burnDown?.items ?? [];
const burnDownSnapshot = burnDown?.current_snapshot ?? null;
const completionQueueItems = completionQueue?.items ?? [];
const completionQueueItems = useMemo(
() => (completionQueue?.items ?? []).filter((item) => {
const readinessMatches =
completionReadinessFilter === "all" ||
(completionReadinessFilter === "pending"
? ["pending", "dispatched", "executing"].includes(item.dispatch_status)
: item.readiness === completionReadinessFilter);
const priorityMatches =
completionPriorityFilter === "all" ||
item.priority_tier === completionPriorityFilter;
return readinessMatches && priorityMatches;
}),
[completionPriorityFilter, completionQueue?.items, completionReadinessFilter]
);
const draftTotal = dedupe?.total_review_drafts ?? reviewDrafts?.total ?? 0;
const activeCount = items.filter((item) =>
["pending", "dispatched", "executing"].includes(item.dispatch_status)
@@ -2776,11 +2835,46 @@ function KnowledgeGovernancePanel({
}
}, [onArchived, staleReviewActions, staleReviewCompletionActions, t]);
const previewCompletionBatch = useCallback(async () => {
setCompletionBatchPreviewAction({
loading: true,
result: null,
error: null,
});
const result = await postJson<KnowledgeStaleOwnerReviewCompletionBatchPreviewResponse>(
`${API_BASE}/api/v1/ai/governance/km-stale-owner-review-completion-queue/batch-preview`,
{
project_id: completionQueue?.project_id ?? staleCandidates?.project_id ?? "awoooi",
status_bucket: completionReadinessFilter,
priority_tiers: completionPriorityFilter === "all"
? ["P0", "P1", "P2"]
: [completionPriorityFilter],
recommended_completion_outcome: "all",
limit: 10,
owner: "operator_console",
owner_note: "completion_queue_batch_preview",
},
15000
);
setCompletionBatchPreviewAction({
loading: false,
result,
error: result ? null : t("staleCandidates.completionQueue.batchPreview.previewFailed"),
});
}, [
completionPriorityFilter,
completionQueue?.project_id,
completionReadinessFilter,
staleCandidates?.project_id,
t,
]);
const staleBatchPreview = staleBatchAction.previewResult;
const staleBatchResult = staleBatchAction.result;
const staleBatchPreviewReady = Boolean(staleBatchPreview?.dry_run_plan_fingerprint);
const staleBatchPreviewStatusKey = kmStaleBatchStatusKey(staleBatchPreview?.status);
const staleBatchResultStatusKey = kmStaleBatchStatusKey(staleBatchResult?.status);
const completionBatchPreview = completionBatchPreviewAction.result;
return (
<section className="border border-[#e0ddd4] bg-white">
@@ -3216,6 +3310,84 @@ function KnowledgeGovernancePanel({
</span>
</div>
</div>
<div className="mb-2 flex flex-wrap items-center justify-between gap-2 border border-[#e0ddd4] bg-[#faf9f3] px-2 py-2">
<div className="flex flex-wrap gap-1">
{(["ready", "blocked", "completed", "failed", "pending", "all"] as const).map((filter) => (
<button
key={filter}
type="button"
onClick={() => setCompletionReadinessFilter(filter)}
className={cn(
"border px-2 py-1 text-[11px] font-semibold leading-4",
completionReadinessFilter === filter
? "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]"
: "border-[#d8d3c7] bg-white text-[#5f5b52] hover:border-[#d9b36f]"
)}
>
{t(`staleCandidates.completionQueue.filters.${filter}` as never)}
</button>
))}
</div>
<div className="flex flex-wrap gap-1">
{(["all", "P0", "P1", "P2"] as const).map((filter) => (
<button
key={filter}
type="button"
onClick={() => setCompletionPriorityFilter(filter)}
className={cn(
"border px-2 py-1 font-mono text-[11px] font-semibold leading-4",
completionPriorityFilter === filter
? "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]"
: "border-[#d8d3c7] bg-white text-[#5f5b52] hover:border-[#9bc7a4]"
)}
>
{filter === "all"
? t("staleCandidates.completionQueue.filters.priorityAll")
: filter}
</button>
))}
</div>
<button
type="button"
onClick={previewCompletionBatch}
disabled={completionBatchPreviewAction.loading}
className="inline-flex items-center gap-1.5 border border-[#d8d3c7] bg-white px-2 py-1 text-xs font-semibold text-[#2e2b26] hover:border-[#1f6feb] hover:bg-[#edf4ff] hover:text-[#0f4fa8] disabled:cursor-not-allowed disabled:opacity-60"
>
<SearchCheck className="h-3.5 w-3.5" aria-hidden="true" />
{completionBatchPreviewAction.loading
? t("staleCandidates.completionQueue.batchPreview.previewing")
: t("staleCandidates.completionQueue.batchPreview.preview")}
</button>
</div>
{completionBatchPreviewAction.error ? (
<p className="mb-2 border border-[#e2a29b] bg-[#fff0ef] px-2 py-1 text-[11px] leading-5 text-[#9f2f25]">
{completionBatchPreviewAction.error}
</p>
) : null}
{completionBatchPreview ? (
<div className="mb-2 border border-[#1f6feb] bg-[#edf4ff] px-2 py-1.5 text-[11px] leading-5 text-[#0f4fa8]">
<p className="font-semibold">
{t("staleCandidates.completionQueue.batchPreview.summary", {
candidates: completionBatchPreview.candidate_count,
previewable: completionBatchPreview.previewable_count,
blocked: completionBatchPreview.blocked_count,
writesKm: String(completionBatchPreview.writes_km),
writesAudit: String(completionBatchPreview.writes_governance_audit),
batchWrites: String(completionBatchPreview.batch_writes_allowed),
})}
</p>
<p className="mt-1 break-all font-mono text-[11px] text-[#5f5b52]">
{t("staleCandidates.completionQueue.batchPreview.planFingerprint", {
fingerprint: completionBatchPreview.dry_run_plan_fingerprint,
})}
</p>
<p className="mt-1 text-[#5f5b52]">
{t("staleCandidates.completionQueue.batchPreview.next", {
action: completionBatchPreview.next_action,
})}
</p>
</div>
) : null}
{completionQueue === null ? (
<p className="text-[11px] leading-5 text-[#8a5a08]">
{t("staleCandidates.completionQueue.unavailable")}