fix(memory): 定宽时间戳存储 + rowid 确定性排序 + 遗留数据归一化

- formatTime 改用固定 30 字节布局(9 位小数秒),让 TEXT 列的字典序与时间序一致(RFC3339Nano 会去掉尾随零产生变宽字符串,秒内会反序)
- memory_items / project / project_chapter 的 ORDER BY 全部补 rowid 兜底,消除同 tick 打平时的顺序不确定
- 开库时幂等归一化变宽的历史值;无法解析的行原样保留,不阻塞开库
- 回归测试:变宽排序反例、同一时间戳插入序、归一化幂等与跳过
This commit is contained in:
AskaEth committed 2026-10-03 16:08:50 +08:00
1 parent 1ac126fc9e
commit 31086ed1ab
10 files changed
+745 -61

No files matched your search

+6 -4
View File
@@ -61,7 +61,8 @@ func (s *Store) AddScoped(ctx context.Context, actorID string, item MemoryItem)
}
// ListScope returns every item of scope visible to personaID, newest first,
// applying the design §3 read rules:
// applying the design §3 read rules (rowid breaks created_at ties so items
// written within the same clock tick keep a stable newest-first order):
//
// - ScopeUser: every user-scope item (globally shared across personas).
// - ScopePersona: every persona-scope item. Other personas are read-only but
@@ -82,14 +83,15 @@ func (s *Store) ListScope(ctx context.Context, scope Scope, personaID string) ([
rows, err := s.db.QueryContext(ctx, `SELECT id, persona_id, role_id, project_id, scope, kind,
content, tags, confidence, source_conversation, created_at, updated_at,
decay_weight FROM memory_items WHERE scope = ?
ORDER BY created_at DESC`, string(scope))
ORDER BY created_at DESC, rowid DESC`, string(scope))
if err != nil {
return nil, fmt.Errorf("memory: list scope items: %w", err)
}
return scanItems(rows, "scope item")
}
// ListProject returns every memory item linked to projectID, newest first. A
// ListProject returns every memory item linked to projectID, newest first, with
// rowid breaking created_at ties so same-tick writes keep a stable order. A
// project-scoped memory carries a ProjectID that links to the projectindex
// hierarchy (design §3); this is the deep-search entry point — callers combine
// it with the projectindex store to walk 项目索引 → 项目描述 → 章节内容. Project
@@ -102,7 +104,7 @@ func (s *Store) ListProject(ctx context.Context, projectID string) ([]MemoryItem
rows, err := s.db.QueryContext(ctx, `SELECT id, persona_id, role_id, project_id, scope, kind,
content, tags, confidence, source_conversation, created_at, updated_at,
decay_weight FROM memory_items WHERE project_id = ?
ORDER BY created_at DESC`, projectID)
ORDER BY created_at DESC, rowid DESC`, projectID)
if err != nil {
return nil, fmt.Errorf("memory: list project items: %w", err)
}