doatlas-2 / artifacts /api-server /src /lib /capability /external-knowledge /internal-experience.adapter.ts
Iostream-Li's picture
Add files using upload-large-folder tool
ff78003 verified
/**
* 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 }
: {}),
},
};
},
};