package memory import ( "context" "database/sql" "fmt" ) // 本文件是 D3「入库前审查」(design/23 §15.1 D3)的 schema 侧。 // // 为什么是一张新表而不是塞进 memory_tidy_candidates:设计稿要的是**每一次 // 入库前审查都留一条结构化记录**(决定 / 依据 / 命中的既有 id / 是否降级, // §15 D3-8),而 tidy 候选表是「人工确认队列」(pending/applied/rejected), // 语义不同、生命周期也不同 —— 审查是已经生效的事实留痕,不是待批准的提议。 // 两表共用同一套 id/时间戳口径(主键 id、created_at),词表口径见各列注释。 // // 表结构的权威机制仍是 internal/db 的迁移(tidy 的 0019 是范例);本表**尚未 // 登记迁移**,改由 NewStore 幂等前滚,与 memory_items / tidy 两表同一手法 // (store_schema.go 的口径:记忆存储必须自包含、可在任意 *sql.DB 上使用)。 // 迁移登记留给主会话(见 MTB-report.md 的「未做」一节),以免与并行切片 // 抢迁移编号。 // createMemoryIngestReviewsTable 是入库前审查的结构化留痕(design/23 §15 D3-8)。 // 刻意**不存记忆正文**:命中的既有 id 足以让 owner-only 的读侧回表取正文, // 而正文不进日志/不进这张表,是把「记忆正文不进日志」这条落成结构。 const createMemoryIngestReviewsTable = ` 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 )` const createMemoryIngestReviewsItemIndex = ` CREATE INDEX IF NOT EXISTS idx_memory_ingest_reviews_item_id ON memory_ingest_reviews (item_id)` const createMemoryIngestReviewsCreatedIndex = ` CREATE INDEX IF NOT EXISTS idx_memory_ingest_reviews_created_at ON memory_ingest_reviews (created_at)` // ensureIngestReviewSchema 幂等地创建入库前审查的留痕表与索引。 func ensureIngestReviewSchema(ctx context.Context, db *sql.DB) error { for _, ddl := range []string{ createMemoryIngestReviewsTable, createMemoryIngestReviewsItemIndex, createMemoryIngestReviewsCreatedIndex, } { if _, err := db.ExecContext(ctx, ddl); err != nil { return fmt.Errorf("memory: ingest review schema: %w", err) } } return nil }