Files
Cyrene_v2/internal/runtime/memory/retention_schema.go
T
AskaEth 765ef167bd fix(memory): 复审 p2 返工 —— SQL 空串字面量、两处过期注释、人工路径档位
复审 p2-1items(tip cc6904b)要求的三处,逐条:

1) SQL 双引号改单引号(唯一返工项):
   - preinsert_store.go 的 revertMergedSQL:`merged_into = ""` → `''`。今天功能等价,
     但靠的是 SQLite「双引号标识符解析不到 ⇒ 回退成字符串字面量」的 legacy 行为;
     这是一条**改数据**的语句,还被 RevertMerged 与整理侧回滚共用,不该依赖回退。
   - retention_migration_test.go 的 `WHERE retention_tier = ""` 一并统一成 `''`,
     并在原位留下「为什么」。
2) 两处过期注释:
   - retention_schema.go:「反过来(先自举、后迁移)也成立」是**假结论**,改为
     「生产顺序成立;反向顺序在 0019 处即失败(duplicate column name: status,既有问题)」。
   - store.go 的 ExpiresAt:「本字段目前只写不读」已半过期(Add 现在会把最终落库
     时刻回填到返回值),改成「读回语义是半开的」:Add 返回的条目有值,从库里读回的
     恒为零值。
3) 人工路径(塔裁定「人工动作默认应为 long」):包内的 `RememberManual`(「记住 X」)
   显式钉 `RetentionTier = long`,留存规则因此不许把人工明确要求记住的东西降成临时层
   (extract_test.go 加断言)。面板网关那条(adminAddMemory 直调 Add、不传档位)
   按塔的口径不在本片、留给后续切片 —— 本片对它的唯一影响见回报。

不动(按塔的裁定):preinsert.go 514 行(dev 既有 tech debt)、
design/03 §10.5 与 design/23 §18 的章节(塔的账)、gateway 人工路径。

回归:go build ./... + go vet ./... + go test -count=1 ./internal/runtime/memory/... ./internal/db/...
全绿,内存包 -race 亦绿。
2026-10-26 13:24:35 +08:00

58 lines
2.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package memory
import (
"context"
"database/sql"
"fmt"
"strings"
)
// 本文件是「留多久 / 干什么用」轴(迁移 0026)的 schema 前滚:给**既有**的
// memory_items 补 `retention_tier` 列,再建读侧索引。
//
// 为什么需要在这里前滚:`createMemoryItemsTable` 是 `CREATE TABLE IF NOT EXISTS`,
// 它只对**新库**生效 —— 既有部署的表早就建好了,新列不会自己长出来。这与
// `ensureProjectIDColumn` / `ensureTidyMemoryColumns` 是同一手法、同一原因
// (记忆存储必须自包含、可在任意 *sql.DB 上使用)。
//
// 🔴 与迁移的分工(两份真源,必须同形):权威机制是
// `internal/db/migrations/0026_memory_retention_tier`;这里是「任意 *sql.DB」的
// 前滚。启动顺序是「先 MigrateUp、后 NewStore」(cmd/cyrene/wire.go),因此生产
// 库里 0026 先落地、这里走到 `duplicate column name` 是**预期路径**。
//
// ⚠️ **反向顺序不成立**(别把上面那句读成「两个方向都行」):先 NewStore、后
// MigrateUp 会在 **0019** 就先炸(`duplicate column name: status` —— 自举早已把
// 整理列建好),这是既有问题、与本片无关,因此本仓的可迁移顺序只有「先迁移」。
//
// 顺序**必须**是「建表 → 前滚列 → 建索引」:索引依赖的列在既有库上还不存在时,
// 先建索引会报 "no such column: retention_tier"(tidy_schema.go 记过同型的教训)。
// ensureRetentionSchema 幂等地给 memory_items 补上档位列与读侧索引。
func ensureRetentionSchema(ctx context.Context, db *sql.DB) error {
if err := ensureRetentionTierColumn(ctx, db); err != nil {
return err
}
if _, err := db.ExecContext(ctx, createMemoryItemsRetentionTierIndex); err != nil {
return fmt.Errorf("memory: retention tier index: %w", err)
}
return nil
}
// ensureRetentionTierColumn 在列缺失时补加 `retention_tier`,已存在时忽略
// SQLite 的 "duplicate column name"(那是预期路径,不是错误)。
//
// 默认值 = 空串 = 尚未分型:既有行因此**行为逐字不变**(不设过期、读路径照常可见),
// 本片也刻意**不**在启动时回填它们的档位 —— 回填是一次写全表的动作,该由整理侧
// 显式做(「按空串过滤」正是那个入口),不该藏在组合根的启动路径里。
func ensureRetentionTierColumn(ctx context.Context, db *sql.DB) error {
_, err := db.ExecContext(ctx, `ALTER TABLE memory_items
ADD COLUMN retention_tier TEXT NOT NULL DEFAULT ''`)
if err == nil {
return nil
}
if strings.Contains(err.Error(), "duplicate column name") {
return nil
}
return fmt.Errorf("memory: roll forward retention_tier column: %w", err)
}