fix(km): bound knowledge readbacks under pool pressure
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m19s
CD Pipeline / build-and-deploy (push) Successful in 4m56s
CD Pipeline / post-deploy-checks (push) Has been cancelled

This commit is contained in:
ogt
2026-07-09 19:34:29 +08:00
parent eb1c5849ca
commit 0f3f4af907
5 changed files with 215 additions and 65 deletions

View File

@@ -64,6 +64,7 @@ interface ListResponse {
categories: CategoryCount[]
asset_taxonomy?: AssetTaxonomyCount[]
readback_status?: 'ready' | 'degraded' | string
degraded_reason_code?: string | null
operator_stage?: string | null
next_step?: string | null
writes_on_read?: boolean
@@ -496,7 +497,7 @@ export default function KnowledgeBasePage({
const [assetTaxonomy, setAssetTaxonomy] = useState<AssetTaxonomyCount[]>(ASSET_TAXONOMY_FALLBACK_COUNTS)
const [loading, setLoading] = useState(true)
const [entryFetchError, setEntryFetchError] = useState<string | null>(null)
const [entryReadback, setEntryReadback] = useState<Pick<ListResponse, 'readback_status' | 'operator_stage' | 'next_step'> | null>(null)
const [entryReadback, setEntryReadback] = useState<Pick<ListResponse, 'readback_status' | 'degraded_reason_code' | 'operator_stage' | 'next_step'> | null>(null)
const [governanceTelemetry, setGovernanceTelemetry] = useState<KnowledgeGovernanceTelemetry>({
staleCandidates: null,
ownerReviews: null,
@@ -541,6 +542,7 @@ export default function KnowledgeBasePage({
setAssetTaxonomy(data.asset_taxonomy?.length ? data.asset_taxonomy : ASSET_TAXONOMY_FALLBACK_COUNTS)
setEntryReadback({
readback_status: data.readback_status ?? 'ready',
degraded_reason_code: data.degraded_reason_code ?? null,
operator_stage: data.operator_stage ?? null,
next_step: data.next_step ?? null,
})
@@ -574,23 +576,15 @@ export default function KnowledgeBasePage({
setGovernanceLoading(true)
try {
const apiBase = process.env.NEXT_PUBLIC_API_URL ?? ''
const [staleCandidates, ownerReviews, burnDown, completionQueue, ragStats] = await Promise.all([
fetch(`${apiBase}/api/v1/ai/governance/km-stale-candidates?project_id=awoooi&limit=5`)
.then(res => res.ok ? res.json() : null)
.catch(() => null),
fetch(`${apiBase}/api/v1/ai/governance/km-stale-owner-reviews?project_id=awoooi&dispatch_status=pending&limit=5`)
.then(res => res.ok ? res.json() : null)
.catch(() => null),
fetch(`${apiBase}/api/v1/ai/governance/km-stale-owner-review-burndown?project_id=awoooi&limit=5`)
.then(res => res.ok ? res.json() : null)
.catch(() => null),
fetch(`${apiBase}/api/v1/ai/governance/km-stale-owner-review-completion-queue?project_id=awoooi&status_bucket=all&limit=5`)
.then(res => res.ok ? res.json() : null)
.catch(() => null),
fetch(`${apiBase}/api/v1/knowledge/rag/stats?project_id=${encodeURIComponent(KNOWLEDGE_PROJECT_ID)}`)
.then(res => res.ok ? res.json() : null)
.catch(() => null),
])
const fetchOptionalJson = async (path: string) => fetch(`${apiBase}${path}`)
.then(res => res.ok ? res.json() : null)
.catch(() => null)
const staleCandidates = await fetchOptionalJson('/api/v1/ai/governance/km-stale-candidates?project_id=awoooi&limit=5')
const ownerReviews = await fetchOptionalJson('/api/v1/ai/governance/km-stale-owner-reviews?project_id=awoooi&dispatch_status=pending&limit=5')
const burnDown = await fetchOptionalJson('/api/v1/ai/governance/km-stale-owner-review-burndown?project_id=awoooi&limit=5')
const completionQueue = await fetchOptionalJson('/api/v1/ai/governance/km-stale-owner-review-completion-queue?project_id=awoooi&status_bucket=all&limit=5')
const ragStats = await fetchOptionalJson(`/api/v1/knowledge/rag/stats?project_id=${encodeURIComponent(KNOWLEDGE_PROJECT_ID)}`)
setGovernanceTelemetry({
staleCandidates,
@@ -694,8 +688,11 @@ export default function KnowledgeBasePage({
)
const totalCount = categories.reduce((sum, c) => sum + c.count, 0)
const isKnowledgeListUnavailable = Boolean(entryFetchError)
const isKnowledgeListDegraded = entryReadback?.readback_status === 'degraded'
const knowledgeReadbackReady = !loading && !isKnowledgeListUnavailable && !isKnowledgeListDegraded
const readbackStatus = entryReadback?.readback_status ?? null
const isKnowledgeSourceBacked = Boolean(readbackStatus?.startsWith('source_backed'))
const isKnowledgeListDegraded = Boolean(readbackStatus && readbackStatus !== 'ready')
const hasKnowledgePayload = entries.length > 0 || total > 0 || categories.some(category => category.count > 0)
const knowledgeReadbackReady = !loading && !isKnowledgeListUnavailable && (!isKnowledgeListDegraded || hasKnowledgePayload)
const localeCode = params.locale === 'en' ? 'en-US' : 'zh-TW'
const formatCount = useCallback(
(value: number) => value.toLocaleString(localeCode),
@@ -1430,15 +1427,27 @@ export default function KnowledgeBasePage({
<div className="min-w-0">
<div className="flex items-center gap-2 text-xs font-label text-status-warning">
<TriangleAlert className="h-4 w-4 shrink-0" aria-hidden="true" />
<span>{entryFetchError ? t('dataChain.errorTitle') : t('dataChain.degradedTitle')}</span>
<span>
{entryFetchError
? t('dataChain.errorTitle')
: isKnowledgeSourceBacked
? t('dataChain.sourceBackedTitle')
: t('dataChain.degradedTitle')}
</span>
</div>
<p className="mt-1 text-xs font-body leading-5 text-secondary">
{entryFetchError
? t('dataChain.errorDescription', { reason: entryFetchError })
: t('dataChain.degradedDescription', {
stage: entryReadback?.operator_stage ?? '--',
next: entryReadback?.next_step ?? '--',
})}
: isKnowledgeSourceBacked
? t('dataChain.sourceBackedDescription', {
reason: entryReadback?.degraded_reason_code ?? '--',
stage: entryReadback?.operator_stage ?? '--',
next: entryReadback?.next_step ?? '--',
})
: t('dataChain.degradedDescription', {
stage: entryReadback?.operator_stage ?? '--',
next: entryReadback?.next_step ?? '--',
})}
</p>
</div>
<button