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
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:
@@ -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")}
|
||||
|
||||
Reference in New Issue
Block a user