package memory import ( "context" "crypto/rand" "encoding/hex" "fmt" "time" ) // 本文件是 memory_items 的**写入侧行装配**(唯一 INSERT 语句、id 与时间戳分配), // 从 store.go 拆出来只为守 AGENTS §10 的文件行数上限(≤500 行)与「模块按职责拆 // 源文件」那条规范 —— 口径逐字未变。 // // 与 store_rows.go(读侧行映射)成对:那里是「列 → MemoryItem」,这里是 // 「MemoryItem → 列」。两边的列序必须**同一套**:scanItem 读 15 列, // 本文件的 INSERT 也写 15 列(`… temporal, sensitive` 收尾)。 // insertMemoryItemSQL 是全仓唯一的一条 memory_items INSERT。列序与 store_rows.go // 的 scanItem 同源(同一张表的同一套列),新增列一律追加在末尾。 // // 🔴 本语句**不经** `status` / `expires_at` / `retention_tier` / `temporal` / // `sensitive` 的默认值路径:前四者由 `ingestGatePlan` 与条目自身给出(档位与时效 // 是**同一个判定**的产物,见 store.go 的 Add),`sensitive` 由条目给出(提取器判据 // + 白名单兜底,见 sensitive.go),一句话都写死在这里,别处不再拼第二份。 const insertMemoryItemSQL = `INSERT INTO memory_items (id, persona_id, role_id, project_id, scope, kind, content, tags, confidence, source_conversation, created_at, updated_at, decay_weight, status, merged_into, expires_at, retention_tier, temporal, sensitive) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` // insertItem 落一行 memory_items。调用方(Add)负责在此前完成规范化、时间戳与 // 档位判定;这里只做「条目 → 列」的映射。 func (s *Store) insertItem(ctx context.Context, item MemoryItem, tagsJSON []byte, plan ingestGatePlan) error { _, err := s.db.ExecContext(ctx, insertMemoryItemSQL, item.ID, item.PersonaID, item.RoleID, item.ProjectID, string(item.Scope), string(item.Kind), item.Content, string(tagsJSON), item.Confidence, item.SourceConversation, formatTime(item.CreatedAt), formatTime(item.UpdatedAt), item.DecayWeight, plan.insertStatus(), plan.absorbedID(), plan.expiresAt(), plan.retention(), item.temporalColumn(), item.sensitiveColumn()) if err != nil { return fmt.Errorf("memory: insert item: %w", err) } return nil } // applyTimestamps 分配 id 与缺失的 CreatedAt/UpdatedAt 值。 // 仅在无法生成新 id 时返回错误。 func applyTimestamps(item MemoryItem) (MemoryItem, error) { now := time.Now().UTC() if item.ID == "" { id, err := newID() if err != nil { return MemoryItem{}, err } item.ID = id } if item.CreatedAt.IsZero() { item.CreatedAt = now } if item.UpdatedAt.IsZero() { item.UpdatedAt = now } return item, nil } // newID 返回一个 128 位密码学随机 id,十六进制编码。它避开 // UUID 依赖,使本包保持自包含。rand.Read 只在 // 平台熵源损坏时失败,因此错误向上传播。 func newID() (string, error) { b := make([]byte, 16) if _, err := rand.Read(b); err != nil { return "", fmt.Errorf("memory: generate id: %w", err) } return hex.EncodeToString(b), nil }