feat(governance): surface km archive audit history
All checks were successful
Code Review / ai-code-review (push) Successful in 12s
Type Sync Check / check-type-sync (push) Successful in 27s
CD Pipeline / tests (push) Successful in 4m9s
CD Pipeline / build-and-deploy (push) Successful in 3m57s
CD Pipeline / post-deploy-checks (push) Successful in 1m45s

This commit is contained in:
Your Name
2026-05-20 09:52:30 +08:00
parent 967d4b77b6
commit 14697ba20e
8 changed files with 305 additions and 0 deletions

View File

@@ -74,6 +74,15 @@ type GovernanceQueueItem = {
human_owner?: string | null;
kb_draft_entry_id?: string | null;
worker_status?: string | null;
dry_run_plan_fingerprint?: string | null;
archived_count?: number | null;
stale_ratio_snapshot?: {
stale_count?: number | null;
total_count?: number | null;
stale_ratio?: number | null;
threshold?: number | null;
stale_days?: number | null;
} | null;
};
type GovernanceQueueResponse = {
@@ -776,6 +785,36 @@ function draftDedupeGroupForQueueItem(
return groups.find((group) => group.governance_event_id === item.governance_event_id) ?? null;
}
function kmArchiveTraceForDedupeGroup(
group: KnowledgeReviewDraftDedupeGroup,
queueItems: GovernanceQueueItem[]
) {
return queueItems
.filter((item) =>
item.governance_event_id === group.governance_event_id &&
(
item.executor_type === "hermes_km_review_dedupe_owner_archive" ||
item.executor_type === "hermes_km_stale_ratio_recheck"
)
)
.sort((a, b) =>
String(b.completed_at ?? b.started_at ?? b.created_at ?? "").localeCompare(
String(a.completed_at ?? a.started_at ?? a.created_at ?? "")
)
)
.slice(0, 3);
}
function kmArchiveExecutorKey(executorType?: string | null) {
if (
executorType === "hermes_km_review_dedupe_owner_archive" ||
executorType === "hermes_km_stale_ratio_recheck"
) {
return executorType;
}
return "unknown";
}
function kmDedupeActionKey(action?: string | null) {
if (
action === "owner_review_canonical_then_archive_duplicates" ||
@@ -1747,6 +1786,7 @@ function KnowledgeGovernancePanel({
);
const previewKey = groupArchiveStatusKey(previewResult?.status);
const finalResultKey = groupArchiveStatusKey(finalResult?.status);
const archiveTrace = kmArchiveTraceForDedupeGroup(group, items);
return (
<div
key={group.governance_event_id}
@@ -1789,6 +1829,61 @@ function KnowledgeGovernancePanel({
})}
</p>
</div>
<div className="mt-2 border border-[#e0ddd4] bg-[#faf9f3] px-2 py-1.5">
<p className="font-semibold text-[#2e2b26]">
{t("archiveHistory.title")}
</p>
{archiveTrace.length === 0 ? (
<p className="mt-1 text-[#77736a]">
{t("archiveHistory.empty")}
</p>
) : (
<div className="mt-1 grid gap-1">
{archiveTrace.map((trace) => (
<div
key={trace.id}
className="border border-[#d8d3c7] bg-white px-2 py-1 text-[#5f5b52]"
>
<p>
{t("archiveHistory.item", {
executor: t(
`archiveHistory.executors.${kmArchiveExecutorKey(trace.executor_type)}` as never
),
status: t(
`statuses.${governanceKmDispatchStatusKey(trace.dispatch_status)}` as never
),
stage: t(
`stages.${governanceKmStageKey(trace.workflow_stage)}` as never
),
archived: trace.archived_count ?? "--",
})}
</p>
{trace.dry_run_plan_fingerprint ? (
<p className="mt-1 break-all font-mono text-[11px]">
{t("archiveHistory.fingerprint", {
fingerprint: trace.dry_run_plan_fingerprint,
})}
</p>
) : null}
{trace.stale_ratio_snapshot ? (
<p className="mt-1">
{t("archiveHistory.snapshot", {
stale: trace.stale_ratio_snapshot.stale_count ?? "--",
total: trace.stale_ratio_snapshot.total_count ?? "--",
ratio: typeof trace.stale_ratio_snapshot.stale_ratio === "number"
? formatStaleRatio(trace.stale_ratio_snapshot.stale_ratio)
: "--",
threshold: typeof trace.stale_ratio_snapshot.threshold === "number"
? formatStaleRatio(trace.stale_ratio_snapshot.threshold)
: "--",
})}
</p>
) : null}
</div>
))}
</div>
)}
</div>
<div className="mt-3 flex flex-wrap items-center gap-2">
<button
type="button"