-- 0025_memory_ingest_reviews: 入库前审查(design/23 §15.1 D3)留痕表的**正式迁移号** -- (切片 W5;D3 本体已落地于 6e28b38,当时**刻意没占迁移号**,以免与并行切片抢号)。 -- -- 为什么现在补:本表此前**只**由 `memory.NewStore` 的幂等前滚创建 -- (internal/runtime/memory/preinsert_schema.go)。后果是「库里有这张表」这件事 -- 分裂成两份真源:新库靠自举、迁移账本却不知道它存在;而清理(保留期清扫)要按 -- `created_at` 扫表 —— 一个不在迁移账本里的表,没人能回答「它的形状是谁负责的」。 -- 本迁移把它登记进 schema_migrations(手法照 0019_memory_tidy 对 memory_items -- 整理列 / 候选表 / 运行表的处理)。 -- -- 🔴 **幂等是硬要求**(本迁移的部署面比一般迁移宽):**所有既有部署的 -- `memory_ingest_reviews` 早已存在**(NewStore 自举),只是没有 0025 这一行。 -- 因此 DDL 一律 `IF NOT EXISTS`:重复执行 = 空操作,绝不因为「表已存在」而炸, -- 也不会因为「表已存在」而漏建索引。形状必须与自举 DDL **逐字同形**(少一列就是 -- 「新库有、老库无」的漂移),由 `internal/runtime/memory/preinsert_migration_test.go` -- 的对照测试钉住(迁移建的表 vs 自举建的表,列与索引逐项比对)。 -- -- 命名口径:表名复数、主键 `id`、索引 `idx_{table}_{col}`(援引 AGENTS §10 已登记例外, -- 与既有迁移一致)。 -- -- 时间戳:**只有 created_at**(定宽 UTC 纳秒串,由 internal/runtime/memory.formatTime -- 写入;见该包 timefmt.go 的口径)。本表是 **append-only**:留痕落一行即不再改写, -- 因此按同类逐条样本表 `0017_llm_usage` 的先例**不引入 updated_at** —— 一个永远等于 -- created_at 的列只会撒谎,而且要补它就必须为既有部署再加一条幂等 ALTER, -- 把 DDL 的两份真源变成三份。若将来真出现「留痕被后续动作改写」的需求, -- 那是 shape 变更,按项目惯例开新迁移号。 -- -- 脱敏(design/23 §15 D3-8「记忆正文不进日志」延伸到表结构):本表**没有**承载 -- 记忆正文的列 —— 命中的既有 id(target_id / merged_ids)足以让 owner-only 的 -- 读侧回表取原文,而正文一个字都不进这张表。 CREATE TABLE IF NOT EXISTS memory_ingest_reviews ( id TEXT PRIMARY KEY, item_id TEXT NOT NULL, persona_id TEXT NOT NULL, scope TEXT NOT NULL, project_id TEXT NOT NULL DEFAULT '', decision TEXT NOT NULL, reason TEXT NOT NULL DEFAULT '', veto TEXT NOT NULL DEFAULT '', target_id TEXT NOT NULL DEFAULT '', merged_ids TEXT NOT NULL DEFAULT '[]', score REAL NOT NULL DEFAULT 0, recall_mode TEXT NOT NULL DEFAULT '', degraded INTEGER NOT NULL DEFAULT 0, rule_version TEXT NOT NULL DEFAULT '', session_id TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL ); -- 按被审条目回溯(「这一条记忆当初被谁合了」)。 CREATE INDEX IF NOT EXISTS idx_memory_ingest_reviews_item_id ON memory_ingest_reviews (item_id); -- 读侧倒序(ListIngestReviews)与**保留期清扫的唯一依赖**:批删语句带 -- `WHERE created_at < ? ORDER BY created_at LIMIT ?`,吃得上这条索引, -- 且每批的扫描面有界(internal/runtime/memory/preinsert_gc.go)。 CREATE INDEX IF NOT EXISTS idx_memory_ingest_reviews_created_at ON memory_ingest_reviews (created_at);