feat(memory): 读侧时效闸(四条列表读路径)+ Add 落 expires_at 列

主人裁定「临时记忆到期删除 / 任何调用记忆的地方都要正常调用到」的读侧
第 ① 步(隐藏):design/03 §9.6 / §11.2 的时效闸下沉到共享底层,不写在
任何调用点。

- timegate.go(新):timeGateClause = (expires_at = '' OR expires_at > ?)
  AND (valid_to = '' OR valid_to > ?);timeGateNow() 与列值同源(同一个
  formatTime 的定宽 30 字节 UTC 文本),expiryText() 负责写入侧编码。
- Store.ListByPersona / List / ListScope / ListProject 四条列表读路径
  的 WHERE 在既有 status 闸之后拼上时效闸;Retriever.visibleCandidates
  组合前两条,检索入口因此自动继承同一判据(无第二处判据)。
- Store.Get 与整理/审查的内部召回 SQL 刻意不过闸,理由写在各自注释里
  (listTidyPartitionItemsSQL 原位注释在本切片;listReviewCandidatesSQL
  的原位注释按塔裁定改派 M2,timegate.go 头注登记了这条拆分)。
- Store.Add 的 INSERT 补上 expires_at 列(MemoryItem.ExpiresAt):Add 是
  全仓唯一 INSERT 路径,时效列的真生产者只能在这里,否则闸退化成空壳。
- timegate_test.go(新):过期/失效行在四条路径与 visibleCandidates 上
  都不可见、空值与未来值可见、Get 仍能读过期行、Add 真的落库该列;
  外加时间口径同源钉(文本序==时间序 + 跨格式 'T' vs ' ' 陷阱实证)。

验收:go build ./internal/runtime/... 通过;go vet 通过;go test
./internal/runtime/memory/... 全绿;internal 全包 62 个 ok / 0 FAIL;
GOOS=darwin GOARCH=arm64 交叉构建通过。
This commit is contained in:
AskaEth committed 2026-10-13 19:54:19 +08:00
1 parent ae68bb0241
commit 9a3b3cff8b
5 files changed
+486 -12

No files matched your search

+7
View File
@@ -41,6 +41,13 @@ const tidyStatusActive = "active"
// 最新在前;rowid 打破 created_at 并列,使同一刻写入的条目顺序稳定
// (与 ListByPersona 同一口径)。分区过滤写在 SQL 里,而不是读全表再在内存
// 里筛 —— design/23 §6 要求在分区切分层就隔断。
//
// 🔴 **刻意不过时效闸**(design/03 §11.2「主会话已裁定:保持不过时效闸」,
// 同一取舍的先例见 design/23 §10.1 ①):整理比对的是**正文**,与「这条对外
// 可不可见」无关;已过期的临时记忆随后由后台清扫物理删除(design/23 §19),
// 绝不会被整理「复活」。可见性判据只有一处 —— 四条列表读路径共用 timegate.go
// 的 timeGateClause。**不要再给这条 SQL 加时效闸。** 本文件另两条分区口径
// 查询(listTidyPartitionsSQL / countTidyPartitionItemsSQL)是同一裁定的实例。
const listTidyPartitionItemsSQL = `SELECT id, persona_id, role_id, project_id, scope, kind,
content, tags, confidence, source_conversation, created_at, updated_at,
decay_weight FROM memory_items