dsh-data-domain-knowledge 0.1.0-alpha.13 → 0.1.0-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/README.md +84 -52
  2. package/cordis.patch.yml +21 -17
  3. package/eval/benchmark-extraction-format.mjs +20 -0
  4. package/lib/client.js +2846 -3872
  5. package/lib/doc-page.js +1 -1
  6. package/lib/pages.js +1299 -1660
  7. package/lib/types/axis-registry.d.ts +6 -0
  8. package/lib/types/axis-registry.d.ts.map +1 -1
  9. package/lib/types/axis-registry.js +45 -0
  10. package/lib/types/axis-registry.js.map +1 -1
  11. package/lib/types/diagnosis/cards.d.ts +7 -3
  12. package/lib/types/diagnosis/cards.d.ts.map +1 -1
  13. package/lib/types/diagnosis/cards.js +13 -8
  14. package/lib/types/diagnosis/cards.js.map +1 -1
  15. package/lib/types/diagnosis/run.d.ts +4 -13
  16. package/lib/types/diagnosis/run.d.ts.map +1 -1
  17. package/lib/types/diagnosis/run.js +26 -16
  18. package/lib/types/diagnosis/run.js.map +1 -1
  19. package/lib/types/diagnosis/schema.js +2 -2
  20. package/lib/types/diagnosis/schema.js.map +1 -1
  21. package/lib/types/diagnosis/seed-source.d.ts +1 -1
  22. package/lib/types/diagnosis/seed-source.d.ts.map +1 -1
  23. package/lib/types/diagnosis/seed-source.js +5 -4
  24. package/lib/types/diagnosis/seed-source.js.map +1 -1
  25. package/lib/types/diagnosis/seed.d.ts +4 -4
  26. package/lib/types/diagnosis/seed.d.ts.map +1 -1
  27. package/lib/types/diagnosis/seed.js +6 -8
  28. package/lib/types/diagnosis/seed.js.map +1 -1
  29. package/lib/types/diagnosis/store.d.ts +8 -16
  30. package/lib/types/diagnosis/store.d.ts.map +1 -1
  31. package/lib/types/diagnosis/store.js +41 -66
  32. package/lib/types/diagnosis/store.js.map +1 -1
  33. package/lib/types/diagnosis/types.d.ts +10 -16
  34. package/lib/types/diagnosis/types.d.ts.map +1 -1
  35. package/lib/types/diagnosis/types.js.map +1 -1
  36. package/lib/types/environment/service.d.ts +4 -3
  37. package/lib/types/environment/service.d.ts.map +1 -1
  38. package/lib/types/environment/service.js +91 -23
  39. package/lib/types/environment/service.js.map +1 -1
  40. package/lib/types/environment/types.d.ts +9 -0
  41. package/lib/types/environment/types.d.ts.map +1 -1
  42. package/lib/types/events.d.ts +2 -0
  43. package/lib/types/events.d.ts.map +1 -1
  44. package/lib/types/events.js +13 -0
  45. package/lib/types/events.js.map +1 -1
  46. package/lib/types/extract-contract.d.ts +1 -1
  47. package/lib/types/extract-contract.d.ts.map +1 -1
  48. package/lib/types/extract-contract.js +34 -31
  49. package/lib/types/extract-contract.js.map +1 -1
  50. package/lib/types/extract-model.d.ts +2 -0
  51. package/lib/types/extract-model.d.ts.map +1 -1
  52. package/lib/types/extract-model.js.map +1 -1
  53. package/lib/types/extract-table-contract.d.ts +3 -0
  54. package/lib/types/extract-table-contract.d.ts.map +1 -0
  55. package/lib/types/extract-table-contract.js +30 -0
  56. package/lib/types/extract-table-contract.js.map +1 -0
  57. package/lib/types/extract-table.d.ts +8 -0
  58. package/lib/types/extract-table.d.ts.map +1 -0
  59. package/lib/types/extract-table.js +151 -0
  60. package/lib/types/extract-table.js.map +1 -0
  61. package/lib/types/extraction-runs.d.ts +3 -0
  62. package/lib/types/extraction-runs.d.ts.map +1 -1
  63. package/lib/types/extraction-runs.js +6 -0
  64. package/lib/types/extraction-runs.js.map +1 -1
  65. package/lib/types/index.d.ts.map +1 -1
  66. package/lib/types/index.js +196 -44
  67. package/lib/types/index.js.map +1 -1
  68. package/lib/types/intent/scope.d.ts.map +1 -1
  69. package/lib/types/intent/scope.js +1 -0
  70. package/lib/types/intent/scope.js.map +1 -1
  71. package/lib/types/knowledge-bases.d.ts.map +1 -1
  72. package/lib/types/knowledge-bases.js +3 -2
  73. package/lib/types/knowledge-bases.js.map +1 -1
  74. package/lib/types/knowledge.d.ts +3 -0
  75. package/lib/types/knowledge.d.ts.map +1 -1
  76. package/lib/types/knowledge.js +12 -3
  77. package/lib/types/knowledge.js.map +1 -1
  78. package/lib/types/model-text.d.ts +1 -1
  79. package/lib/types/model-text.d.ts.map +1 -1
  80. package/lib/types/model-text.js +5 -4
  81. package/lib/types/model-text.js.map +1 -1
  82. package/lib/types/pipeline/engine.d.ts +57 -0
  83. package/lib/types/pipeline/engine.d.ts.map +1 -0
  84. package/lib/types/pipeline/engine.js +268 -0
  85. package/lib/types/pipeline/engine.js.map +1 -0
  86. package/lib/types/pipeline/store.d.ts +56 -0
  87. package/lib/types/pipeline/store.d.ts.map +1 -0
  88. package/lib/types/pipeline/store.js +115 -0
  89. package/lib/types/pipeline/store.js.map +1 -0
  90. package/lib/types/pipeline/types.d.ts +82 -0
  91. package/lib/types/pipeline/types.d.ts.map +1 -0
  92. package/lib/types/pipeline/types.js +5 -0
  93. package/lib/types/pipeline/types.js.map +1 -0
  94. package/lib/types/present.d.ts +2 -0
  95. package/lib/types/present.d.ts.map +1 -1
  96. package/lib/types/present.js +9 -4
  97. package/lib/types/present.js.map +1 -1
  98. package/lib/types/runtime.d.ts +20 -1
  99. package/lib/types/runtime.d.ts.map +1 -1
  100. package/lib/types/runtime.js +389 -112
  101. package/lib/types/runtime.js.map +1 -1
  102. package/lib/types/source-evidence.d.ts.map +1 -1
  103. package/lib/types/source-evidence.js +2 -0
  104. package/lib/types/source-evidence.js.map +1 -1
  105. package/lib/types/store/axis-references.js +3 -3
  106. package/lib/types/store/axis-references.js.map +1 -1
  107. package/lib/types/store/migrate.js +1 -1
  108. package/lib/types/store/migrate.js.map +1 -1
  109. package/lib/types/types.d.ts +3 -4
  110. package/lib/types/types.d.ts.map +1 -1
  111. package/lib/types/types.js.map +1 -1
  112. package/lib/types/wiki-materialize.d.ts.map +1 -1
  113. package/lib/types/wiki-materialize.js +3 -15
  114. package/lib/types/wiki-materialize.js.map +1 -1
  115. package/lib/types/wiki-trial-types.d.ts +3 -0
  116. package/lib/types/wiki-trial-types.d.ts.map +1 -1
  117. package/lib/types/wiki-trial.d.ts +2 -0
  118. package/lib/types/wiki-trial.d.ts.map +1 -1
  119. package/lib/types/wiki-trial.js +31 -7
  120. package/lib/types/wiki-trial.js.map +1 -1
  121. package/package.json +6 -3
  122. package/preset/tita-data-agent/agent.cordis.yml +21 -17
  123. package/src/axis-registry.ts +38 -0
  124. package/src/client/AttributionEventGuide.tsx +10 -9
  125. package/src/client/EnvironmentSetup.tsx +35 -15
  126. package/src/client/ExtractionTasksPage.tsx +129 -176
  127. package/src/client/KnowledgeBaseTab.tsx +31 -76
  128. package/src/client/NodeSettings.tsx +2 -3
  129. package/src/client/PipelineNotice.tsx +26 -0
  130. package/src/client/TabGuide.tsx +4 -4
  131. package/src/client/TypeDefinitionDrawer.tsx +23 -8
  132. package/src/client/WikiCardPreview.tsx +43 -10
  133. package/src/client/WikiFlowPanel.tsx +4 -4
  134. package/src/client/architecture.tsx +1 -1
  135. package/src/client/attribution/AttributionTab.tsx +6 -6
  136. package/src/client/attribution/CaseView.tsx +3 -1
  137. package/src/client/attribution/EventView.tsx +14 -4
  138. package/src/client/attribution/RunView.tsx +2 -2
  139. package/src/client/attribution/graph-options.ts +2 -2
  140. package/src/client/pages.tsx +2 -2
  141. package/src/diagnosis/cards.ts +13 -8
  142. package/src/diagnosis/run.ts +27 -17
  143. package/src/diagnosis/schema.ts +2 -2
  144. package/src/diagnosis/seed-source.ts +5 -4
  145. package/src/diagnosis/seed.ts +6 -8
  146. package/src/diagnosis/store.ts +47 -68
  147. package/src/diagnosis/types.ts +10 -16
  148. package/src/environment/service.ts +79 -23
  149. package/src/environment/types.ts +6 -0
  150. package/src/events.ts +12 -0
  151. package/src/extract-contract.ts +6 -3
  152. package/src/extract-model.ts +2 -0
  153. package/src/extract-table-contract.ts +29 -0
  154. package/src/extract-table.ts +126 -0
  155. package/src/extraction-runs.ts +6 -0
  156. package/src/index.ts +156 -45
  157. package/src/intent/scope.ts +1 -0
  158. package/src/knowledge-bases.ts +3 -2
  159. package/src/knowledge.ts +13 -3
  160. package/src/model-text.ts +5 -3
  161. package/src/pipeline/engine.ts +238 -0
  162. package/src/pipeline/store.ts +119 -0
  163. package/src/pipeline/types.ts +74 -0
  164. package/src/present.ts +10 -4
  165. package/src/runtime.ts +350 -110
  166. package/src/source-evidence.ts +1 -0
  167. package/src/store/axis-references.ts +3 -3
  168. package/src/store/migrate.ts +1 -1
  169. package/src/types.ts +3 -4
  170. package/src/wiki-materialize.ts +3 -14
  171. package/src/wiki-trial-types.ts +3 -0
  172. package/src/wiki-trial.ts +26 -7
@@ -12,6 +12,7 @@ export interface CompileModelSelectionView {
12
12
  mode: 'default' | 'fixed'
13
13
  provider?: string
14
14
  model?: string
15
+ reasoningEffort?: string
15
16
  }
16
17
 
17
18
  interface CompileModelCatalogItem {
@@ -21,6 +22,7 @@ interface CompileModelCatalogItem {
21
22
  description?: string
22
23
  available: boolean
23
24
  current?: boolean
25
+ reasoning?: { efforts: Array<{ id: string; name: string; description?: string }>; defaultEffort?: string }
24
26
  }
25
27
 
26
28
  interface CompileModelCatalog {
@@ -50,6 +52,8 @@ export function TypeDefinitionDrawer({
50
52
  }: TypeDefinitionDrawerProps): ReactNode {
51
53
  const [noteDraft, setNoteDraft] = useState(extractionNote)
52
54
  const [modelDraft, setModelDraft] = useState(DEFAULT_MODEL_VALUE)
55
+ const [effortDraft, setEffortDraft] = useState(compileModel.reasoningEffort ?? '__default__')
56
+ useEffect(() => { setEffortDraft(compileModel.reasoningEffort ?? '__default__') }, [compileModel.reasoningEffort])
53
57
  const [catalog, setCatalog] = useState<CompileModelCatalog | null>(null)
54
58
  const [catalogError, setCatalogError] = useState('')
55
59
  useEffect(() => { setNoteDraft(extractionNote) }, [extractionNote])
@@ -64,14 +68,16 @@ export function TypeDefinitionDrawer({
64
68
  if (!open || onSaveCompileModel === undefined) return undefined
65
69
  let cancelled = false
66
70
  setCatalogError('')
67
- setCatalog(null)
68
- void requestJson<CompileModelCatalog>(`/knowledge/models?kb=${encodeURIComponent(kbId)}`).then(result => {
71
+ const route = modelDraft.startsWith('fixed:') ? modelDraft.split(':').slice(1).map(decodeURIComponent) : [catalog?.default.provider, catalog?.default.model]
72
+ const params = new URLSearchParams({ kb: kbId })
73
+ if (route[0] && route[1]) { params.set('provider', route[0]); params.set('model', route[1]) }
74
+ void requestJson<CompileModelCatalog>(`/knowledge/models?${params}`).then(result => {
69
75
  if (!cancelled) setCatalog(result.data ?? null)
70
76
  }).catch(error => {
71
77
  if (!cancelled) setCatalogError(error instanceof Error ? error.message : String(error))
72
78
  })
73
79
  return () => { cancelled = true }
74
- }, [kbId, onSaveCompileModel, open])
80
+ }, [kbId, onSaveCompileModel, open, modelDraft, catalog?.default.provider, catalog?.default.model])
75
81
 
76
82
  const modelOptions = useMemo(() => {
77
83
  const options: SelectMenuOption[] = [{
@@ -100,20 +106,26 @@ export function TypeDefinitionDrawer({
100
106
  return options
101
107
  }, [catalog, compileModel.mode, compileModel.model, compileModel.provider])
102
108
 
103
- const modelChanged = modelDraft !== (compileModel.mode === 'fixed' && compileModel.provider && compileModel.model
109
+ const modelChanged = effortDraft !== (compileModel.reasoningEffort ?? '__default__') || modelDraft !== (compileModel.mode === 'fixed' && compileModel.provider && compileModel.model
104
110
  ? fixedModelValue(compileModel.provider, compileModel.model)
105
111
  : DEFAULT_MODEL_VALUE)
106
112
  const saveCompileModel = async (): Promise<void> => {
107
113
  if (onSaveCompileModel === undefined) return
108
114
  if (modelDraft === DEFAULT_MODEL_VALUE) {
109
- await onSaveCompileModel({ mode: 'default' })
115
+ await onSaveCompileModel({ mode: 'default', ...(effortDraft === '__default__' ? {} : { reasoningEffort: effortDraft }) })
110
116
  return
111
117
  }
112
118
  const selected = catalog?.models.find(item => fixedModelValue(item.provider, item.id) === modelDraft && item.available)
113
119
  if (!selected) return
114
- await onSaveCompileModel({ mode: 'fixed', provider: selected.provider, model: selected.id })
120
+ await onSaveCompileModel({ mode: 'fixed', provider: selected.provider, model: selected.id, ...(effortDraft === '__default__' ? {} : { reasoningEffort: effortDraft }) })
115
121
  }
116
122
 
123
+ const selectedModel = catalog?.models.find(item => modelDraft === DEFAULT_MODEL_VALUE
124
+ ? item.provider === catalog.default.provider && item.id === catalog.default.model
125
+ : fixedModelValue(item.provider, item.id) === modelDraft)
126
+ const effortOptions: SelectMenuOption[] = [{ value: '__default__', label: '跟随模型默认强度' }, ...(selectedModel?.reasoning?.efforts ?? []).map(item => ({ value: item.id, label: item.name }))]
127
+ if (effortDraft !== '__default__' && !effortOptions.some(option => option.value === effortDraft)) effortOptions.push({ value: effortDraft, label: `当前不可用:${effortDraft}`, disabled: true })
128
+
117
129
  return <Drawer open={open} onOpenChange={onOpenChange}>
118
130
  {open && <DrawerContent
119
131
  className={styles.settingsDrawer}
@@ -129,10 +141,13 @@ export function TypeDefinitionDrawer({
129
141
  <div className={styles.settingsSectionHeader}>
130
142
  <div><h3>编译模型</h3></div>
131
143
  </div>
132
- <Field as="div" label="模型" hint={modelDraft === DEFAULT_MODEL_VALUE ? undefined : '固定模型不可用时会降级为规则抽取。'}>
133
- <SelectMenu value={modelDraft} disabled={busy !== null || (catalog === null && catalogError === '')} onValueChange={setModelDraft}
144
+ <Field as="div" label="模型" hint="用于抽取、归一补判与卡片组织;任务启动时保存实际选择。">
145
+ <SelectMenu value={modelDraft} disabled={busy !== null || (catalog === null && catalogError === '')} onValueChange={value => { setModelDraft(value); setEffortDraft('__default__') }}
134
146
  options={modelOptions} placeholder="加载 DSH 模型目录…" aria-label="编译模型" />
135
147
  </Field>
148
+ <Field as="div" label="思考强度" hint={selectedModel?.reasoning?.efforts.length ? '档位来自当前模型的 DSH 目录。' : '当前模型未提供可选档位,使用模型默认值。'}>
149
+ <SelectMenu value={effortDraft} options={effortOptions} onValueChange={setEffortDraft} disabled={busy !== null || !selectedModel?.reasoning?.efforts.length} aria-label="编译模型思考强度" />
150
+ </Field>
136
151
  {catalog?.default && !catalog.default.available && <p className={styles.kbHint} role="status">
137
152
  DSH 默认模型不可用;可在 DSH 设置中修复,或选择固定模型。
138
153
  </p>}
@@ -20,8 +20,8 @@
20
20
  */
21
21
 
22
22
  import { useEffect, useMemo, useState, type ReactNode } from 'react'
23
- import { AlertCircle, Check, GitCompare, RefreshCw } from '@tita-agent/ui/icons'
24
- import { Button, cn } from '@tita-agent/ui/components'
23
+ import { AlertCircle, Check, CircleDot, GitCompare, RefreshCw } from '@tita-agent/ui/icons'
24
+ import { Badge, Button, cn } from '@tita-agent/ui/components'
25
25
  import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@tita-agent/ui/overlays'
26
26
  import type { AssetRef } from './asset-images.ts'
27
27
  import type { VersionManagerEntry } from '@tita-agent/ui/versions'
@@ -34,13 +34,10 @@ import { STATUS_VIEW } from './WikiCardRow.tsx'
34
34
  import { styles } from './styles.ts'
35
35
  import { formatDateTime } from './format.ts'
36
36
  import type { CardRelatedView, KnowledgeCardView, WikiCardVersionView } from './KnowledgeBaseTab.tsx'
37
+ import type { AttributionEvent } from './attribution/model.ts'
37
38
 
38
- /**
39
- * 正文档里那一节「相关」的小节名。与 `wiki-finalize.ts` 的 `RELATED_HEADING` 同值 ——
40
- * 客户端不能 import 服务端模块(那边拉 node: 内置),所以这里重列一次,并由一条测试
41
- * 钉住两边相等(同 `SOURCE_FORMAT_LABEL` 那类「契约重列」的处置)。
42
- */
43
- const RELATED_SECTION = ['相关']
39
+ /** 正文里的生成式导航小节名;它们由结构化引用展示,不重复留在正文。 */
40
+ const RELATED_SECTION = ['相关', '关联 Event', '关联 Events', '关联事件']
44
41
 
45
42
  /** 一张卡片的完整预览数据(正文 + 版本 + 图片 + 相关 + 坐标名字)。 */
46
43
  export interface CardPreviewDetail {
@@ -90,6 +87,9 @@ export interface WikiCardPreviewProps {
90
87
  onCompare?: (entry: VersionManagerEntry) => void
91
88
  versionDiff?: { data?: VersionContentDiffData; loading: boolean; error?: string }
92
89
  onOpenRelated: (link: { entityType: string; entityId: string; title: string }) => void
90
+ /** 打开 Event 栏并定位到这张卡片引用的事件。 */
91
+ onOpenEvent: (eventId: string) => void
92
+ events?: AttributionEvent[]
93
93
  /**
94
94
  * SourceRaw 的 docId → 文档标题。
95
95
  *
@@ -113,7 +113,7 @@ function sourceRefsText(refs: string[], titles?: Map<string, string>): string {
113
113
 
114
114
  export function WikiCardPreview({
115
115
  kbId, detail, versionEntries, currentVersionId, loading,
116
- onChanged, onOpenRelated, sourceTitles,
116
+ onChanged, onOpenRelated, onOpenEvent, events = [], sourceTitles,
117
117
  onCompare, versionDiff = { loading: false },
118
118
  }: WikiCardPreviewProps): ReactNode {
119
119
  const [recompiling, setRecompiling] = useState(false)
@@ -122,9 +122,27 @@ export function WikiCardPreview({
122
122
  const status = STATUS_VIEW[detail.card.status]?.label ?? detail.card.status
123
123
  const refs = sourceRefsText(detail.card.sourceRefs, sourceTitles)
124
124
  const knowledge = useCardKnowledge(kbId, detail.card.entityId, sourceTitles)
125
+ const relatedEvents = useMemo(() => {
126
+ const raw = detail.card.metadata?.event_refs
127
+ let ids: string[] = []
128
+ if (Array.isArray(raw)) ids = raw.filter((item): item is string => typeof item === 'string')
129
+ else if (typeof raw === 'string') {
130
+ try {
131
+ const parsed: unknown = JSON.parse(raw)
132
+ if (Array.isArray(parsed)) ids = parsed.filter((item): item is string => typeof item === 'string')
133
+ } catch { /* 无效派生引用不显示 */ }
134
+ }
135
+ const byId = new Map(events.map(event => [event.id, event]))
136
+ return [...new Set(ids)].map(id => byId.get(id) ?? { id }).map(event => ({
137
+ id: event.id,
138
+ title: 'title' in event ? event.title : event.id,
139
+ summary: 'summary' in event ? event.summary : '',
140
+ occurredAt: 'occurredAt' in event ? event.occurredAt : undefined,
141
+ }))
142
+ }, [detail.card.metadata, events])
125
143
  useEffect(() => { setCompareOpen(false) }, [detail.card.entityId])
126
144
  /**
127
- * 先移除旧版本只在面板下方由关系图展示的「相关」区块,再补普通引用清单。
145
+ * 移除旧版本正文里的生成式导航区块,再显示结构化卡片与 Event 引用。
128
146
  *
129
147
  * 投影顺序固定为:物化正文 → 去掉旧导航区 → 补引用清单。
130
148
  */
@@ -206,6 +224,21 @@ export function WikiCardPreview({
206
224
  {recompiling ? '编译中…' : '按坐标重新编译'}</Button>}
207
225
  />
208
226
 
227
+ {relatedEvents.length > 0 && <section className="flex min-w-0 flex-col gap-1.5" aria-label="关联 Event">
228
+ <h3 className="m-0 flex items-center gap-1.5 text-[11px] font-semibold text-tita-ink">
229
+ <CircleDot size={12} aria-hidden="true" />关联 Event <Badge tone="neutral">{relatedEvents.length}</Badge>
230
+ </h3>
231
+ <ul className="m-0 grid list-none gap-1 p-0">
232
+ {relatedEvents.map(event => <li key={event.id} className="min-w-0 rounded-md border border-tita-line px-2 py-1.5">
233
+ <Button variant="ghost" size="sm" className="h-auto max-w-full justify-start px-0 py-0 text-left text-[10.5px]" onClick={() => onOpenEvent(event.id)}>
234
+ <span className="min-w-0 truncate">{event.title}</span>
235
+ {event.occurredAt && <span className="shrink-0 text-[9px] text-tita-ink-faint">· {event.occurredAt}</span>}
236
+ </Button>
237
+ {event.summary && <p className="m-0 mt-0.5 line-clamp-2 text-[10px] leading-relaxed text-tita-ink-muted">{event.summary}</p>}
238
+ </li>)}
239
+ </ul>
240
+ </section>}
241
+
209
242
  {recompileNotice && <div className={cn(styles.cardRecompileNotice, recompileNotice.tone === 'error' ? styles.cardRecompileNoticeError : styles.cardRecompileNoticeSuccess)} role="status">
210
243
  {recompileNotice.tone === 'error' ? <AlertCircle size={12} aria-hidden="true" /> : <Check size={12} aria-hidden="true" />}
211
244
  <span>{recompileNotice.text}</span>
@@ -165,7 +165,7 @@ const STAGES: Stage[] = [
165
165
  body: [
166
166
  '**三个轴分别归一**:先找 canonical,再找 alias,最后才新建候选。匹配不唯一时,**不猜**。',
167
167
  '每条知识绑定一个**坐标**:三个轴实体 id 的**复合键**。**Coordinate 不是实体**,没有自己的表。',
168
- '同义知识合并,`sources` 追加;相互矛盾的说法保留,并标记为 disputed。',
168
+ '归一化文本一致时合并并追加来源;语义相似只记候选,不自动合并。相互矛盾的说法需显式标记 disputed。',
169
169
  ],
170
170
  rule: {
171
171
  order: '2',
@@ -176,7 +176,7 @@ const STAGES: Stage[] = [
176
176
  produces: {
177
177
  layer: 'Canonical Knowledge',
178
178
  ask: '在哪里',
179
- note: '轴已归一并绑成 id;同义合并过,冲突留着',
179
+ note: '轴绑定为 id;同文事实合并,语义候选待确认',
180
180
  },
181
181
  },
182
182
  {
@@ -198,7 +198,7 @@ const STAGES: Stage[] = [
198
198
  title: '坐标决定卡片身份,卡名只是显示',
199
199
  body: '一个坐标最多一张卡。坐标是数据库主键;标题由对象、场景和动作生成,只是**显示名**,不参与判重。',
200
200
  },
201
- model: '系统(确定)',
201
+ model: '系统分组,可选模型组织',
202
202
  produces: {
203
203
  layer: 'Wiki Card',
204
204
  ask: '怎么组织这些知道的东西',
@@ -217,7 +217,7 @@ const STAGES: Stage[] = [
217
217
  '**Knowledge 是证据层,不是第二个检索入口**:命中 Section 后,再带出对应知识、状态和出处。',
218
218
  '向量、关键词和图谱 boost 都属于**可重建的派生层**(`derived.db`)。**轴注册表不是索引**,而是坐标解析时使用的命名真源。',
219
219
  ],
220
- model: '不调模型',
220
+ model: '系统切分 + 向量编码',
221
221
  produces: {
222
222
  layer: '可检索 Wiki',
223
223
  /* 派生层,所以没有 ask —— 它不是实体,不回答上面那三个问题。 */
@@ -286,7 +286,7 @@ icon: Database, lane: 'knowledge', tab: 'knowledge', knowledgeTab: 'source-raw',
286
286
  { label: '物化 WikiCard', detail: '按 (对象, 动作, 场景) 分组,卡名由公式推导(对象名 + 场景 + 动作名);小节与字段作为组织提示,卡片直接入库,review 与备注在预览中处理', state: 'done' },
287
287
  { label: '建索引', detail: '切召回单元、写索引页与相关卡片、检查断链,不调模型', state: 'done' },
288
288
  ],
289
- status: 'live', statusNote: '四段都已实现:抽 Knowledge、归一坐标、按坐标物化卡片、切召回单元并建索引(模型不可用时退回规则抽取,此时没有坐标,正文按小节切)。入库后的 review 与备注在卡片的预览侧栏里。',
289
+ status: 'live', statusNote: '导入默认只保存来源;抽取 Knowledge / Event、归一、物化分别运行,也可全链路运行。未变文档复用检查点;构建成功后统一发布索引。模型不可用时任务明确失败,可查看输出与历史后重试。',
290
290
  icon: RefreshCw, lane: 'knowledge', tab: 'knowledge', knowledgeTab: 'wiki',
291
291
  },
292
292
  build: {
@@ -37,8 +37,9 @@ interface DiagnosisPayload {
37
37
 
38
38
  /** 服务端的三种来源 → 徽标上那句人话。 */
39
39
  function sourceLabelOf(source: string | undefined): string {
40
- if (source === 'cards') return '02_wiki/diagnosis'
41
- if (source === 'jsonl') return '05_diagnosis(旧格式)'
40
+ if (source === 'cards') return '可复用诊断卡片'
41
+ if (source === 'records') return 'Event 与排查记录'
42
+ if (source === 'jsonl') return '旧格式历史快照'
42
43
  return '还没有排查知识'
43
44
  }
44
45
 
@@ -224,10 +225,9 @@ export function AttributionTab({ visible, activeQuestion, onOpenKnowledge }: {
224
225
  }, [visible, reloadToken, applyPayload])
225
226
 
226
227
  /**
227
- * 载入内置示例:**显式的一次写**,写进的是这个库自己的 `01_source_raw/` 与 `02_wiki/`。
228
+ * 载入内置示例:**显式的一次写**,将来源记录与可复用诊断卡写入当前知识库。
228
229
  *
229
- * 载完重新读一次,让图、案例、来源徽标全部换成真实数据 —— 载入之后它们就是普通卡片,
230
- * 可以照常改、照常删,所以说明里必须把这件事讲清楚。
230
+ * 载完重新读一次,让图、只读排查视图、来源徽标全部换成当前库的数据。
231
231
  */
232
232
  const loadSample = async (): Promise<void> => {
233
233
  setSampleBusy(true); setSampleError(null)
@@ -236,7 +236,7 @@ export function AttributionTab({ visible, activeQuestion, onOpenKnowledge }: {
236
236
  const body = await response.json() as { ok?: boolean; data?: DiagnosisPayload; error?: { message?: string } }
237
237
  if (!response.ok || body.ok === false) throw new Error(body.error?.message ?? `HTTP ${response.status}`)
238
238
  applyPayload(body.data ?? {})
239
- setSampleNotice('已载入内置示例:4 份来源写进 01_source_raw/,26 张卡片写进 02_wiki/diagnosis/。它们就是普通来源与卡片,可以照常改或删。')
239
+ setSampleNotice('已载入内置示例:7 条来源记录、23 张可复用诊断卡;3 条排查记录以只读案例视图呈现。')
240
240
  } catch (error) {
241
241
  setSampleError(error instanceof Error ? error.message : String(error))
242
242
  } finally {
@@ -46,7 +46,9 @@ export function CaseView({ cases, activeId, onActiveChange }: {
46
46
  <h3 className="text-[13px] font-semibold leading-snug">{active.title}</h3>
47
47
  <p className="mt-0.5 text-[10.5px] text-tita-ink-muted">{active.id} · 耗时 {active.durationMinutes} min</p>
48
48
  </div>
49
- <Badge tone="success">已确认根因</Badge>
49
+ <Badge tone={active.rootCauseStatus === 'unconfirmed' ? 'warning' : 'success'}>
50
+ {active.rootCauseStatus === 'unconfirmed' ? '根因未确认' : '已确认根因'}
51
+ </Badge>
50
52
  </header>
51
53
 
52
54
  {/* ── wiki 段 ── */}
@@ -1,5 +1,5 @@
1
1
  import { CircleDot, FileText, GitBranch, Search } from '@tita-agent/ui/icons'
2
- import type { ReactNode } from 'react'
2
+ import { useEffect, type ReactNode } from 'react'
3
3
  import { Badge, Card } from '@tita-agent/ui/components'
4
4
  import type { AttributionEvent } from './model.ts'
5
5
 
@@ -15,7 +15,12 @@ function identityLabel(event: AttributionEvent): string {
15
15
  return '来源内独立记录'
16
16
  }
17
17
 
18
- export function EventView({ events }: { events: AttributionEvent[] }): ReactNode {
18
+ export function EventView({ events, focusEventId }: { events: AttributionEvent[]; focusEventId?: string }): ReactNode {
19
+ useEffect(() => {
20
+ if (!focusEventId) return
21
+ const target = document.getElementById(`knowledge-event-${focusEventId}`)
22
+ target?.scrollIntoView({ block: 'center', behavior: 'smooth' })
23
+ }, [events, focusEventId])
19
24
  if (events.length === 0) return <p className="py-5 text-center text-[11px] text-tita-ink-muted">还没有从文档中提取 Event。</p>
20
25
 
21
26
  const groups = new Map<string, { label: string; events: AttributionEvent[] }>()
@@ -40,7 +45,12 @@ export function EventView({ events }: { events: AttributionEvent[] }): ReactNode
40
45
  <GitBranch size={12} className="mt-0.5 shrink-0 text-tita-primary" />
41
46
  {group.label}
42
47
  </h3>
43
- {group.events.map(event => <Card key={event.id} className="gap-2 p-2.5">
48
+ {group.events.map(event => <article
49
+ key={event.id}
50
+ id={`knowledge-event-${event.id}`}
51
+ className={event.id === focusEventId ? 'rounded-lg ring-2 ring-tita-primary ring-offset-2 ring-offset-tita-surface' : undefined}
52
+ aria-label={event.id === focusEventId ? `已定位 Event:${event.title}` : undefined}
53
+ ><Card className="gap-2 p-2.5">
44
54
  <header className="flex items-start gap-2">
45
55
  <div className="min-w-0 flex-1">
46
56
  <h4 className="text-[12px] font-semibold leading-snug text-tita-ink">{event.title}</h4>
@@ -92,7 +102,7 @@ export function EventView({ events }: { events: AttributionEvent[] }): ReactNode
92
102
  : <span className="text-[9.5px] text-tita-ink-faint">暂无可显示的引文</span>}
93
103
  {event.missing.length > 0 && <p className="text-[9.5px] leading-relaxed text-tita-warning">待补:{event.missing.join('、')}</p>}
94
104
  </footer>
95
- </Card>)}
105
+ </Card></article>)}
96
106
  </section>)}
97
107
  </div>
98
108
  }
@@ -177,9 +177,9 @@ export function RunView({ data, cases, symptoms, symptomId, onSymptomChange, onS
177
177
  不是一次进行中的排查 —— 没有「本次跑了哪些检查点、看到什么」的现场状态,
178
178
  按一个按钮只能编出一份路径来,而那正好是这个产品最不该做的事。
179
179
  真实的写回发生在排查进行时:Agent 调 `POST /diagnosis/run` 落一条
180
- `agent_diagnosis_case` 的 SourceRaw,顺手提一张案例卡片等人确认。
180
+ `agent_diagnosis_case` 的 SourceRaw;Case 由这条记录只读派生,Knowledge / Event 由独立链路抽取和归一。
181
181
  */}
182
- <Button className="w-full" size="sm" disabled title="写回发生在排查进行时:Agent 用 POST /diagnosis/run 落一条诊断日志并提议案例卡片。这一屏是只读的复盘视图。">
182
+ <Button className="w-full" size="sm" disabled title="Agent 在排查结束时用 POST /diagnosis/run 落 SourceRaw;Case 只读派生,Knowledge / Event 由知识链路处理。">
183
183
  写回由排查进行时完成(这一屏只读)
184
184
  </Button>
185
185
  </Card>
@@ -162,8 +162,8 @@ export function nodeStyleFor(
162
162
  lineDash: isGap ? [4, 3] : undefined,
163
163
  zIndex: node.layer === 'instance' ? 0 : 1,
164
164
  // 圆里放 id(直径由复用次数决定,中文标题塞不进);胶囊放标题。
165
- // 案例例外:它的标题来自案例卡片、不含 id,而案例要靠 id 才能在复盘里被指认 ——
166
- // 所以由画布把 id 拼回去,标题本身保持单一份来源。
165
+ // Case 是排查 SourceRaw 的只读投影,不是 WikiCard。视图用症状标题;画布补上稳定的
166
+ // Case id,方便在复盘里指认这次排查。
167
167
  labelText: shape === 'circle' ? node.id : node.kind === 'case' ? `${node.id} ${node.title}` : node.title,
168
168
  labelPlacement: 'center',
169
169
  labelFill: palette.text,
@@ -228,7 +228,7 @@ function App(): ReactNode {
228
228
 
229
229
  if (route.kind === 'run') return <RunPage kb={route.kb} runId={route.run} />
230
230
 
231
- if (route.kind === 'tasks') return <PageShell title="抽取任务" column="trial"><ExtractionTasksPage kb={route.kb} /></PageShell>
231
+ if (route.kind === 'tasks') return <PageShell title="知识处理" column="trial"><ExtractionTasksPage kb={route.kb} /></PageShell>
232
232
 
233
233
  if (route.kind === 'help') {
234
234
  if (route.topic === 'overview') {
@@ -248,7 +248,7 @@ function App(): ReactNode {
248
248
  <p>整页的地址只有这几种:</p>
249
249
  <p><code>/knowledge/pages/trial?kb=&lt;知识库 id&gt;</code> · 抽取试跑<br />
250
250
  <code>/knowledge/pages/run?kb=&lt;知识库 id&gt;&amp;run=&lt;轨迹 id&gt;</code> · 一次抽取的报告<br />
251
- <code>/knowledge/pages/tasks?kb=&lt;知识库 id&gt;</code> · 抽取任务<br />
251
+ <code>/knowledge/pages/tasks?kb=&lt;知识库 id&gt;</code> · 知识处理与任务历史<br />
252
252
  <code>/knowledge/pages/help/compile</code> · 编译和召回链路<br />
253
253
  <code>/knowledge/pages/help/intent</code> · 意图的逻辑<br />
254
254
  <code>/knowledge/pages/help/attribution</code> · 智能归因事件<br />
@@ -7,7 +7,7 @@ import type {
7
7
  } from './types.js'
8
8
 
9
9
  /**
10
- * 诊断卡片的**编解码**:markdown 卡片 ⇄ 图上的节点与边(0021 §6.3)。
10
+ * 诊断域 WikiCard 的**编解码**:卡片 ⇄ 图上的可复用节点与关系(0021 §6.3)。
11
11
  *
12
12
  * ## 为什么这一层必须存在
13
13
  *
@@ -16,8 +16,8 @@ import type {
16
16
  * 编译器连 `diagnosis/hypothesis` 这个 entity_type 都不认 —— 于是它既不会被 wikiNote 化,
17
17
  * 也不会进向量索引,**召回永远召不到它**。而「遇到类似问题先召回」正是这个产品的主张。
18
18
  *
19
- * 这一层把图变成卡片的**投影**:卡片是手写的(人写、Agent 提议后由人确认),
20
- * 图是算出来的。§4「存图、显树」讲的是同一件事 —— 存的是知识,图是它的一种读法。
19
+ * 这一层把预先整理好的可复用诊断 Knowledge 投影为 WikiCard。新排查记录走 SourceRaw,
20
+ * Case 由读取层派生;`diagnosis/case` 只保留旧格式读取与迁移兼容。
21
21
  *
22
22
  * ## 卡片三段式与「谁由卡片定义」的边界
23
23
  *
@@ -31,9 +31,10 @@ import type {
31
31
  *
32
32
  * | 图上的东西 | 谁定义 | 为什么 |
33
33
  * | --- | --- | --- |
34
- * | symptom / hypothesis / check_point / failure_reason / case 节点 | **卡片** | 这五类是人确认过的知识(§6.4) |
34
+ * | symptom / hypothesis / check_point / failure_reason 节点 | **WikiCard** | 这些是可复用知识(§6.4) |
35
35
  * | asset 节点 | 引用反推 | 它随数据目录同步,不手写;卡片里只留 id |
36
- * | 现象→假设 / 假设→检查点 / 假设→失败原因 / 检查点→资产 / 资产→假设 / 案例→现象 边 | **卡片** | 都是卡片字段的确定性投影 |
36
+ * | 现象→假设 / 假设→检查点 / 假设→失败原因 / 检查点→资产 / 资产→假设 边 | **WikiCard** | 都是卡片字段的确定性投影 |
37
+ * | 历史 case 节点与案例→现象边 | **旧 WikiCard** | 仅为旧库迁移与读取兼容;新 Case 由排查 SourceRaw 派生 |
37
38
  * | evidence / cause 节点,produces / supports / refutes / confirmed_as 边 | **一次排查** | 它们是一次执行的结果,不是知识;由 `01_source_raw/agent_diagnosis_case/` 那条 SourceRaw 读时解析出来 |
38
39
  *
39
40
  * 最后一行不是妥协,是分层:把「上次查到什么」写进卡片,等于让一份可复用的知识
@@ -286,9 +287,13 @@ function splitSections(body: string): DiagnosisCardSection[] {
286
287
  /* ─────────────────────────────────────────────── 图 → 卡片 ── */
287
288
 
288
289
  /**
289
- * 类型层与案例 → 卡片。
290
+ * 类型层与历史 Case 卡 → 卡片的兼容编解码器。
290
291
  *
291
- * `asset` / `evidence` / `cause` 节点**不产出卡片**:它们不是手确认过的知识(§6.4)。
292
+ * 新样例会用它写入预先整理的可复用诊断 Knowledge;旧 Case 卡仅用于读取和兼容迁移。
293
+ * 新排查不会把 Case 写成 WikiCard。
294
+ *
295
+ * `case`、`asset` / `evidence` / `cause` 节点**不产出新卡片**:前者是排查记录视图,
296
+ * 后三者不是可复用 Knowledge(§6.4)。
292
297
  * 被引用的资产会在反序列化时按 id 补一个最小节点回来。
293
298
  */
294
299
  export function graphToCards(graph: AttributionGraphData, cases: AttributionCase[] = []): DiagnosisCard[] {
@@ -395,7 +400,7 @@ export interface CompiledDiagnosis {
395
400
  /**
396
401
  * 卡片 → 图。
397
402
  *
398
- * 只产出「卡片定义得了」的那部分(类型层 + 引用层的资产 + 案例,以及六种卡片边);
403
+ * 只产出当前 WikiCard 定义得了的部分(Knowledge 类型 + 引用层资产及其关系);历史 Case 卡仅用于读取兼容;
399
404
  * `evidence` / `cause` 与四条执行边由一次排查写入(`agent_diagnosis_case` 的 SourceRaw),
400
405
  * 不在这里合成 —— 否则一次执行的结果会被伪装成可复用的知识。
401
406
  */
@@ -1,5 +1,5 @@
1
1
  import type {
2
- AttributionEdge, AttributionGraphData, AttributionNode, CasePathStep, DiagnosisRunEvidence, DiagnosisRunRecord,
2
+ AttributionCase, AttributionEdge, AttributionGraphData, AttributionNode, CasePathStep, DiagnosisRunEvidence, DiagnosisRunRecord,
3
3
  } from './types.js'
4
4
  import type { DiagnosisCard } from './cards.js'
5
5
 
@@ -12,15 +12,15 @@ import type { DiagnosisCard } from './cards.js'
12
12
  * (分类、版本、检索),就得是一条 SourceRaw。自造一个 jsonl 格式,等于在知识库旁边
13
13
  * 再养一套只读不进链路的数据 —— 这也是 0021 §0.3 撤销 `05_diagnosis/` 的直接原因。
14
14
  *
15
- * ## 它与案例卡片的区别
15
+ * ## 它与排查视图的关系
16
16
  *
17
17
  * | | 谁写 | 性质 |
18
18
  * | --- | --- | --- |
19
19
  * | 这条 SourceRaw(`agent_diagnosis_case`) | **Agent**,每次排查结束自动写 | 跑出来的**过程记录** |
20
- * | 案例卡片(`diagnosis/case`) | Agent 提议 → **人确认** | 归纳出来的**结论** |
20
+ * | 排查视图(Case) | 从这条记录读取 | 只读展示,不再生成第二张 WikiCard |
21
21
  *
22
- * 同一件事可以有两条记录,它们不是重复:一个回答「当时怎么走的」,一个回答「结论是什么」。
23
- * 而案例卡片必须经人确认 —— 它落在类型层收敛的那一侧,见 0021 §6.1。
22
+ * Event 与 Knowledge 在归一层并列;WikiCard 由 Knowledge 组织生成,并可指向 Event。
23
+ * Case 仅是一次排查记录的展示视图,不是新的持久化实体。
24
24
  *
25
25
  * ## 三段式
26
26
  *
@@ -212,18 +212,28 @@ export function runReferenceProblems(record: DiagnosisRunRecord, knownIds: Reado
212
212
  return problems
213
213
  }
214
214
 
215
- /**
216
- * 把一次排查归纳成一张**案例卡片**(还只是提议,要人确认才生效)。
217
- *
218
- * 归纳这一步是「过程记录 → 可复用知识」的那一跳,也是唯一需要人看的一跳:
219
- * 路径里哪几步其实是误导、根因一句话怎么写,都是判断题。
220
- */
221
- /**
222
- * 一次排查的案例卡片草案(0044 前叫 `caseProposalOf`)。
223
- *
224
- * 改名是因为它产出的东西**直接落卡**,不再是一条提议:名字里留着 `Proposal` 会让下一个
225
- * 读的人继续在两个时代之间猜。函数体一行没动 —— 它本来就是「从一条排查记录编出一张案例卡」。
226
- */
215
+ /** Read-only case presentation projected from a diagnosis source record. */
216
+ export function caseViewOfRun(record: DiagnosisRunRecord, symptomTitle: string): AttributionCase {
217
+ const rootCause = record.rootCause?.text ?? '(这次没有确认根因)'
218
+ const firstMisleading = record.misleading?.[0]
219
+ return {
220
+ id: `CASE-${record.runId}`,
221
+ title: symptomTitle,
222
+ symptomId: record.symptomId,
223
+ sections: [
224
+ { heading: '现象', body: `入口现象 \`${record.symptomId}\`(${symptomTitle}),开始于 ${record.startedAt}。` },
225
+ { heading: '排查过程', body: record.path.map(step => `- \`${step.label}\` ${step.note}${step.refuted ? '(否定)' : ''}`).join('\n') || '(没有记录路径)' },
226
+ { heading: '检查结果', body: record.evidence.map(item => `- \`${item.checkPointId}\`:${item.result ?? ''}(${item.verdict})${item.why ? `,${item.why}` : ''}`).join('\n') || '(没有记录检查结果)' },
227
+ { heading: '根因与处理', body: [rootCause, record.rootCause?.fixAction].filter(Boolean).join('\n\n') },
228
+ ],
229
+ path: record.path,
230
+ durationMinutes: record.durationMinutes,
231
+ ...(firstMisleading ? { misleading: firstMisleading } : {}),
232
+ rootCause,
233
+ rootCauseStatus: record.rootCause ? 'confirmed' : 'unconfirmed',
234
+ }
235
+ }
236
+ /** 兼容旧版案例卡片格式的转换器;新排查只写 SourceRaw,不调用此函数。 */
227
237
  export function caseCardOf(record: DiagnosisRunRecord, symptomTitle: string): DiagnosisCard {
228
238
  const rootCause = record.rootCause?.text ?? '(这次没有确认根因)'
229
239
  const firstMisleading = record.misleading?.[0]
@@ -247,8 +247,8 @@ export function validateDiagnosis(graph: AttributionGraphData): DiagnosisIssue[]
247
247
  if (!node.verdict) issues.push(fieldIssue(node, 'verdict', '证据必须给出结论:supports / refutes / inconclusive'))
248
248
  if (!node.why?.trim()) issues.push(fieldIssue(node, 'why', '证据必须说明「为什么这个结果算数或不算数」—— 采信与剔除都得能说理由'))
249
249
  }
250
- // `case` 节点只是图上的桩:案例的正文与路径住在案例卡片里,由
251
- // DiagnosisSnapshot.cases 校验,不在节点上重复要求。
250
+ // `case` 节点只是兼容旧图的桩;当前排查视图由 SourceRaw 记录派生,
251
+ // 新排查不会再写 diagnosis/case WikiCard。
252
252
  }
253
253
 
254
254
  // ── 第二层:引用完整性 ────────────────────────────────────────────────────
@@ -6,11 +6,12 @@
6
6
  * 所以 seed 也走完整条动线:
7
7
  *
8
8
  * ```
9
- * 这些文本(SourceRaw) → 五类卡片 → 编译 → 图
9
+ * 原文(SourceRaw) → 抽取 Knowledge / Event → 归一
10
+ * 排查运行记录(SourceRaw) → 只读 Case 视图
10
11
  * ```
11
12
  *
12
- * 手册与复盘是**人写的散文**(就是下面这些字面量),卡片是从它们编译出来的结构化提取。
13
- * 两者内容会有重叠 —— 那是正常的:卡片是散文的投影,不是它的替代品。
13
+ * 手册与复盘是**人写的散文**(就是下面这些字面量)。载入示例只保存来源与可复用诊断卡;
14
+ * Case 从独立的排查运行记录派生,不生成第二张诊断卡。
14
15
  */
15
16
  import { SEED_CASES, SEED_RUNS } from './seed.js'
16
17
  import { renderRunSource } from './run.js'
@@ -83,7 +84,7 @@ export const SEED_HANDBOOK = `# 日报指标异常排查手册(内部版)
83
84
  同一批假设被反复验证,而永远不会被剪掉 —— 图上会长出一堆僵尸边。
84
85
  `
85
86
 
86
- /** 三篇复盘:案例卡片的出处。正文就是案例的散文段(现象 / 影响面 / 排查过程 / 根因 / 修复)。 */
87
+ /** 三篇复盘:供后续抽取 Knowledge / Event 的来源文档。 */
87
88
  export function seedCaseSources(): SeedSource[] {
88
89
  return SEED_CASES.map(item => ({
89
90
  id: item.id,
@@ -19,7 +19,7 @@ import type { AttributionCase, AttributionGraphData, DiagnosisRunRecord } from '
19
19
  * | `question` | 人 | 手册与复盘 —— 卡片 `source_refs` 指向这里 |
20
20
  * | `agent_diagnosis_case` | Agent | 一次排查的过程记录(`SEED_RUNS`) |
21
21
  *
22
- * `SEED_GRAPH` 里**只有卡片定义得了的东西**:类型层、引用层资产、案例桩,以及六种卡片边。
22
+ * `SEED_GRAPH` 里有可复用诊断 Knowledge 与引用层资产;历史案例桩只用于旧格式兼容夹具。
23
23
  * `evidence` / `cause` 与四条执行边由 `SEED_RUNS` 读时解析出来 —— 把「上次查到什么」
24
24
  * 写进卡片,等于让一份可复用的知识绑死在一次执行上。
25
25
  */
@@ -122,10 +122,8 @@ export const SEED_GRAPH: AttributionGraphData = {
122
122
  { id: 'metric_daily_gmv', kind: 'asset', layer: 'reference', title: 'metric_daily_gmv', assetType: 'metric' },
123
123
  { id: 'bi_daily_dashboard', kind: 'asset', layer: 'reference', title: 'bi_daily_dashboard', assetType: 'linkage' },
124
124
 
125
- // ── 实例层:案例节点是**图上的桩**(正文与有序路径住在案例卡片里) ──────
126
- // 标题必须与 `SEED_CASES[].title` 是同一个字符串,否则同一份知识会有两份标题
127
- // (而且还真的不一致过:「只剩」vs「只有」)。画布要在标签前显示案例 id,
128
- // 那是渲染层的事(见 `nodeStyleFor` 的 labelText)。
125
+ // ── 兼容层:旧格式里的案例节点桩。新样例的排查记录由 SEED_RUNS 派生为只读视图。 ──
126
+ // 保留稳定 ID,供旧卡片编解码与历史图迁移夹具使用。
129
127
  { id: 'CASE-001', kind: 'case', layer: 'instance', title: '日报 GMV 为 0' },
130
128
  { id: 'CASE-002', kind: 'case', layer: 'instance', title: '订单数只有昨日 12%' },
131
129
  { id: 'CASE-003', kind: 'case', layer: 'instance', title: 'DAU 突增 3.1 倍' },
@@ -197,7 +195,7 @@ export const SEED_GRAPH: AttributionGraphData = {
197
195
  ],
198
196
  }
199
197
 
200
- /** 三个 seed 案例卡片,与上面图谱里的节点 id 对齐(0021 §11)。 */
198
+ /** 旧版示例 Case 投影夹具;仅供历史卡片编解码与来源文案使用,当前载入流程不落盘。 */
201
199
  export const SEED_CASES: AttributionCase[] = [
202
200
  {
203
201
  id: 'CASE-001',
@@ -280,8 +278,8 @@ export const SEED_CASES: AttributionCase[] = [
280
278
  * 三次排查的**过程记录**(`agent_diagnosis_case`)。
281
279
  *
282
280
  * 它们与 `SEED_CASES` 一一对应,但**不是同一份东西**:这里是 Agent 跑出来的原始记录
283
- * (每条检查点真跑了什么、看到什么、这个结果算不算数),`SEED_CASES` 是之后归纳出来的
284
- * 案例卡片(要经人确认才生效)。一个回答「当时怎么走的」,一个回答「结论是什么」。
281
+ * (每条检查点真跑了什么、看到什么、这个结果算不算数);当前 Case 视图从运行记录派生,
282
+ * `SEED_CASES` 仅保留作旧卡片格式夹具。
285
283
  *
286
284
  * `E7` 是 CASE-001 里那次**贵的否定**:T7.2 直连明细对账推翻了 H7。它不在案例的
287
285
  * 主路径上(主路径早就确认了 H1),但它是这次排查最贵的一条知识 —— 40 分钟的误导段