feat(memory): 记忆整理「试算(dry-run)」入口 + 面板卡(契约 v0.75)
主人要「现在就想看到在 736 条上它会建议合并哪些」,而不是等 23:00 的低峰首跑。
- 后端:Tidier.DryRun + TidyService.DryRun,**复用同一条扫描→召回→判定→构造候选路径**(抽出 tidyRoundPolicy,生产轮与试算轮只差「候选出口」与「召回降级策略」两处;禁止第二套实现)
- 🔴 零副作用:不写 memory_tidy_candidates / memory_tidy_runs,绝不改 memory_items 任何列;机械证明 = 跑前跑后 cyrene.db 与 -wal 的 SHA256 一致(测试断言「表行数不变」)
- 失败码诚实:500 tidy_unavailable / 409 tidy_disabled / 500 tidy_recall_unavailable(**embedding 不可用不降级成 0 条**)/ 503 tidy_timeout / 500 tidy_dry_run_failed;**空数组唯一含义 = 算过了没建议**
- 端点 POST /api/v1/admin/memories/tidy/dry-run(writeAuth);面板「记忆」页加试算卡(点了才跑、无轮询、三态分开、明写「仅试算,不会改动任何记忆」)
- 真机 e2e(741 条活库副本):76 ms / 15 分区 / 53 候选 / 41 skipped / 1 vetoed(low_confidence);样例 merge reason=fact_subset score=1.0000、link reason=similar_but_not_contained score=0.9248
- 变异:dry-run 顺手写库 ⇒ 两条测试红(行数 0→2);自造阈值 ⇒ 红;失败回 200 空数组 ⇒ 红
- 如实登记:Compared=688 被 400/分区护栏截断、候选上限 20/分区被打满 ⇒ 护栏值需在 741 条量级重定(非 bug)
This commit is contained in:
1 parent
3d8c30dc99
commit
d6ed9cfa2b
18 files changed
+1631
-79
No files matched your search
@@ -119,6 +119,7 @@ export {
|
||||
adminConfigFacts,
|
||||
adminFlowGraph,
|
||||
adminListNotifications,
|
||||
adminMemoryTidyDryRun,
|
||||
adminObservabilityAudit,
|
||||
adminObservabilityLogs,
|
||||
adminObservabilityMetrics,
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
AdminTraceSpan,
|
||||
AuditRecord,
|
||||
AdminConfigFacts,
|
||||
AdminTidyDryRunResult,
|
||||
BackthinkItem,
|
||||
FlowGraph,
|
||||
SystemBackupResp,
|
||||
@@ -96,6 +97,20 @@ export function adminConfigFacts(): Promise<AdminConfigFacts> {
|
||||
return request<AdminConfigFacts>("/admin/config-facts");
|
||||
}
|
||||
|
||||
/** POST /api/v1/admin/memories/tidy/dry-run — 记忆整理「试算」(契约 v0.75 §4.12)。
|
||||
*
|
||||
* 它跑**同一套**整理规则(与低峰定时轮共用代码路径)并把候选直接回在响应里,
|
||||
* 但**一行都不写**(不写候选表/运行表、不改任何记忆)—— 因此可以在任何时刻、
|
||||
* 在整理器第一次开跑之前,先看清「它会建议合并哪些」。
|
||||
*
|
||||
* ⚠️ 它不是看板数据源:试算是一次真实的规则计算(扫描 + 两两召回 + 包含判定),
|
||||
* **只在用户点击时调用,不要轮询**。
|
||||
* ⚠️ 失败走 `error.code`(tidy_unavailable / tidy_disabled / tidy_recall_unavailable /
|
||||
* tidy_timeout),**不会**回 `candidates: []` —— 空数组只表示「算过了,没有建议」。 */
|
||||
export function adminMemoryTidyDryRun(): Promise<AdminTidyDryRunResult> {
|
||||
return request<AdminTidyDryRunResult>("/admin/memories/tidy/dry-run", { method: "POST" });
|
||||
}
|
||||
|
||||
/** GET /api/v1/admin/notifications?type=&before=&limit= — the unified notification
|
||||
* feed (design/10 §3 #16: audit + channel + backthink + system aggregated).
|
||||
* `type` omitted = all types, `before` = createdAt(ms) cursor for older pages,
|
||||
|
||||
@@ -13,4 +13,5 @@ export * from "./types/adminWrite";
|
||||
export * from "./types/kook";
|
||||
export * from "./types/notification";
|
||||
export * from "./types/configFacts";
|
||||
export * from "./types/memoryTidy";
|
||||
export * from "./types/channelEvents";
|
||||
@@ -0,0 +1,66 @@
|
||||
// 记忆整理「试算」的线上类型(契约 v0.75 §4.12)。
|
||||
// 唯一真源:internal/services/gateway/admin_memory_tidy.go。
|
||||
//
|
||||
// ⚠️ 这份响应**没有**改动任何记忆(`dryRun` 恒为 true)。失败**不会**以
|
||||
// `candidates: []` 的形式出现 —— 失败是错误响应(`error.code` ∈ tidy_unavailable /
|
||||
// tidy_disabled / tidy_recall_unavailable / tidy_timeout / tidy_dry_run_failed),
|
||||
// 因此「空数组」唯一含义是「算过了,这一轮没有建议」。
|
||||
|
||||
/** 一条候选的判定证据。字段名沿用后端 JSON(snake_case,因为复用领域结构体)。 */
|
||||
export interface AdminTidyEvidence {
|
||||
/** 召回用的余弦相似度(只用来召回,**不是**合并判据)。 */
|
||||
similarity?: number;
|
||||
recall_mode?: string;
|
||||
/** a_subset_b / b_subset_a / both / none。 */
|
||||
containment?: string;
|
||||
containment_evidence?: string[];
|
||||
veto_reason?: string;
|
||||
kind_a?: string;
|
||||
kind_b?: string;
|
||||
proposed_kind?: string;
|
||||
/** 被处理条目的原文,供 diff 预览。 */
|
||||
content_a?: string;
|
||||
/** 存活条目的原文,供 diff 预览。 */
|
||||
content_b?: string;
|
||||
}
|
||||
|
||||
/** 一条整理建议(未落库)。 */
|
||||
export interface AdminTidyDryRunCandidate {
|
||||
/** merge(并入)/ link(只建链接,绝不合并)/ archive(一次性事件)/ reclassify。 */
|
||||
action: string;
|
||||
sourceIds: string[];
|
||||
/** merge 时的存活条目(超集);其余动作为空。 */
|
||||
targetId?: string;
|
||||
personaId: string;
|
||||
scope: string;
|
||||
projectId?: string;
|
||||
kind?: string;
|
||||
proposedContent?: string;
|
||||
mergedContent?: string;
|
||||
/** 机器可读理由(如 containment_a_subset_b / similar_but_not_contained)。 */
|
||||
reason: string;
|
||||
ruleVersion: string;
|
||||
scoreMin: number;
|
||||
scoreMax: number;
|
||||
evidence?: AdminTidyEvidence;
|
||||
}
|
||||
|
||||
/** POST /api/v1/admin/memories/tidy/dry-run 的 data。 */
|
||||
export interface AdminTidyDryRunResult {
|
||||
/** 恒为 true:这份响应没有改动任何记忆。 */
|
||||
dryRun: boolean;
|
||||
/** embedding(语义向量召回)| lexical(零依赖词项余弦,进程没接 embedder 时)。 */
|
||||
recallMode: string;
|
||||
durationMs: number;
|
||||
ruleVersion: string;
|
||||
partitions: number;
|
||||
scanned: number;
|
||||
compared: number;
|
||||
pairs: number;
|
||||
vetoed: number;
|
||||
vetoReasons?: Record<string, number>;
|
||||
/** 被护栏截断的分区标签(不是候选)。 */
|
||||
truncated?: string[];
|
||||
/** 恒为数组:空数组 = 「算过了,没有建议」。 */
|
||||
candidates: AdminTidyDryRunCandidate[];
|
||||
}
|
||||
Reference in new issue
Block a user