doatlas-2 / artifacts /api-server /src /lib /capability /external-knowledge /internal-experience.adapter.ts
| /** | |
| * internal-experience.adapter — 真读 PG `capability_data_foundation`。 | |
| * | |
| * **关键反自循环**:默认排除调用方自己的 capability_id。caller 必须显式 | |
| * 传 `include_self: true` 才能读自己的历史 — 这是为了防止 capability | |
| * 把自己上一轮的输出当"经验"再喂回来,放大错误。 | |
| * | |
| * B2 (#255) 只读。B7 (#260) 才写入。 | |
| * | |
| * 支持参数: | |
| * - capability_id 只读这个 capability 的经验(默认排除 caller 自己) | |
| * - include_self boolean,默认 false | |
| * - kind 'param-fingerprint' / 'failure-pattern' / ... 由 B7 定义 | |
| * - since ISO timestamp,只取这之后的行 | |
| */ | |
| import { and, eq, gt, ne, sql } from "drizzle-orm"; | |
| import { capabilityDataFoundation, db } from "@workspace/db"; | |
| import { enforceBudget } from "./budget-guard.ts"; | |
| import { fingerprintParams, recordCall } from "./telemetry.ts"; | |
| import type { | |
| KnowledgeAdapter, | |
| KnowledgeItem, | |
| KnowledgeQuery, | |
| KnowledgeResult, | |
| } from "./types.ts"; | |
| interface InternalParams { | |
| capability_id?: string; | |
| include_self?: boolean; | |
| kind?: string; | |
| since?: string; | |
| } | |
| function readParams(p: Record<string, unknown>): InternalParams { | |
| return { | |
| capability_id: | |
| typeof p["capability_id"] === "string" ? (p["capability_id"] as string) : undefined, | |
| include_self: p["include_self"] === true, | |
| kind: typeof p["kind"] === "string" ? (p["kind"] as string) : undefined, | |
| since: typeof p["since"] === "string" ? (p["since"] as string) : undefined, | |
| }; | |
| } | |
| export const internalExperienceAdapter: KnowledgeAdapter = { | |
| kind: "internal_experience", | |
| status: "real", | |
| async query(q: KnowledgeQuery): Promise<KnowledgeResult> { | |
| enforceBudget(q.capabilityId, "internal_experience"); | |
| const params = readParams(q.params ?? {}); | |
| const limit = Math.min(q.limit ?? 50, 500); | |
| const fp = fingerprintParams({ ...params, limit, caller: q.capabilityId }); | |
| const t0 = Date.now(); | |
| let error: string | null = null; | |
| let excludedSelf = false; | |
| let items: KnowledgeItem[] = []; | |
| // sinceApplied 只有 caller 传 since **且** 解析成功 **且** 真进了 SQL where | |
| // 才为 true。反欺骗:绝不允许 sourceMetadata 暗示 filter 生效但 SQL | |
| // 实际忽略了它(architect 原 caveat #6)。 | |
| let sinceApplied = false; | |
| try { | |
| const conds = []; | |
| if (params.capability_id) { | |
| conds.push(eq(capabilityDataFoundation.capabilityId, params.capability_id)); | |
| } | |
| // 反自循环 gate:除非 include_self=true 且未指定 capability_id, | |
| // 否则永远排除 caller 自己。 | |
| if (!params.include_self) { | |
| conds.push(ne(capabilityDataFoundation.capabilityId, q.capabilityId)); | |
| excludedSelf = true; | |
| } | |
| if (params.kind) conds.push(eq(capabilityDataFoundation.kind, params.kind)); | |
| if (params.since) { | |
| const sinceDate = new Date(params.since); | |
| if (!isNaN(sinceDate.getTime())) { | |
| conds.push(gt(capabilityDataFoundation.createdAt, sinceDate)); | |
| sinceApplied = true; | |
| } | |
| } | |
| const whereExpr = conds.length > 0 ? and(...conds) : undefined; | |
| const rows = await db | |
| .select() | |
| .from(capabilityDataFoundation) | |
| .where(whereExpr ?? sql`TRUE`) | |
| .limit(limit); | |
| items = rows.map((r) => ({ | |
| id: r.id, | |
| score: null, | |
| payload: { | |
| capability_id: r.capabilityId, | |
| kind: r.kind, | |
| payload: r.payload, | |
| source_run_id: r.sourceRunId, | |
| created_at: r.createdAt.toISOString(), | |
| }, | |
| origin: "pg:capability_data_foundation", | |
| })); | |
| } catch (err) { | |
| error = err instanceof Error ? err.message : String(err); | |
| throw err; | |
| } finally { | |
| void recordCall({ | |
| adapter: "internal_experience", | |
| capabilityId: q.capabilityId, | |
| latencyMs: Date.now() - t0, | |
| hitCount: items.length, | |
| cacheHit: null, | |
| error, | |
| paramsFingerprint: fp, | |
| }); | |
| } | |
| return { | |
| kind: "internal_experience", | |
| items, | |
| cursor: null, | |
| sourceMetadata: { | |
| primarySource: "pg:capability_data_foundation", | |
| limit, | |
| excludedSelf, | |
| callerCapability: q.capabilityId, | |
| // 透传 filter 让 caller 能验证 where 子句真的进了 SQL — | |
| // 否则"返空"无法区分 (a) 真无数据 vs (b) filter 没拼对。 | |
| ...(params.capability_id !== undefined | |
| ? { filteredCapability: params.capability_id } | |
| : {}), | |
| ...(params.kind !== undefined ? { kindFilter: params.kind } : {}), | |
| // since 必须真应用到 SQL where 才 echo;invalid date string 时透传 | |
| // sinceProvided 但 sinceFilterApplied=false,告诉 caller "你传了但 | |
| // 我没用",杜绝 metadata 撒谎(architect caveat #6 fix)。 | |
| ...(params.since !== undefined | |
| ? { sinceProvided: params.since, sinceFilterApplied: sinceApplied } | |
| : {}), | |
| }, | |
| }; | |
| }, | |
| }; | |