refactor(memory): M45 r1 复审返工 —— temporal 位只放开 N6 位置判据
按 r1 复审裁定(p1-3items / fix-then-merge)重做三处: P1-1/P1-2:删掉自造的六条语义安全阀(temporalSanityVeto)与约定词表 (retentionStatesAgreement)。理由被复审用两条生产行否证:它们既把该救的行 杀了(「…睡得比平时早」被习惯词、「…以后不用再惦记」被永久化标记),又挡不住 同义异形的约定(说定 / 约了)。现在 `one_off` 的全部作用是**放开 N6 的位置判据**: judgeExtractionRetention 新增 temporal 入参 → judgeRetentionTransient → judgeRetentionTimepointWide(位置内命中仍返 n6_timepoint,只有靠放开位置才命中 才返新增的 n6_timepoint_wide,供观测区分)。N1~N5、N7、护栏①②、N6 的 1~7 条 语义安全阀一字不动;durable / recurring 仍直接判 long(fail-safe 方向)。 P1-3:上界口径改为「时点词存在 ∧ 语义安全阀全过 ∧ 当前 long」——不依赖任何 标注,可逐条列出。实测(生产库只读副本,快照 2026-10-26):最近 500 条 active 能翻 13 条、**逐条都是「约定 + 今天没走成」**;全量 3694 条能翻 26 条(13 条 约定 + 13 条状态行)。旧的「句首时点词」口径是同义反复,已删。 已知代价(写进 temporal.go 头注与 TestTemporalOneOffWidenedPositionTouchesAgreements, 不藏在词表后面):位置窗口今天正是那批约定行的唯一保护,放开后只要模型标 one_off 它们就会判 temp;而复审点名的两条目标行本片救不了(被语义阀挡,与位置 无关)。因此这一位在当前语料上**没有可测收益**,是否保留是产品裁决。 P2-1:store.go 503 → 497 行,拆出 store_temporal.go(哨兵错误 + 字段校验 + 列值编码)。 P2-2:temporal.go 头注改准 —— `''` 是「0027 之前的历史行 + 没做时间性判断的 写入路径(ManualExtractor / 网关手写 / 整理回填,它们由调用方显式给档位)」。 P2-3:报告补语境(retention_tier 非空仅 26 行、temp 1 行、temporal 列当时还不 存在 ⇒ 重放结论对新写入没有预测力)。 测试:验收点 ①~⑤ 按新语义重写;新增「位置放开可打红」的变异靶子与「N7 位置 不放宽」「无时点词时这一位不做事」「两条目标行救不了」「约定行会翻(已知风险)」 五条边界断言。变异实测:unlocksTimepointPosition 改恒 false ⇒ 三条用例红。 回归:go build ./... + go vet ./... 全绿;memory/db 包测试与 memory -race 全绿。
This commit is contained in:
1 parent
91101dedfb
commit
3b9ae3f918
9 files changed
+412
-370
No files matched your search
@@ -137,14 +137,15 @@ type MemoryItem struct {
|
||||
RetentionTier RetentionTier
|
||||
|
||||
// Temporal 是**提取器给出的时间性元数据位**(迁移 0027、temporal.go):
|
||||
// one_off / recurring / durable / unknown,空串 = 未分型(历史行与
|
||||
// 非提取器写入路径)。它只由提取器在**产出侧**填,判定侧
|
||||
// one_off / recurring / durable / unknown,空串 = 未分型(历史行与没做时间性
|
||||
// 判断的写入路径,口径见 temporal.go 头注)。它只由提取器在**产出侧**填,判定侧
|
||||
// (resolveRetention)读它一位,不让任何规则去猜「这句话说的是不是今天的事」。
|
||||
//
|
||||
// ⚠️ **与 ExpiresAt 同样是「只写不读」的列**:`Add` 把它写进 `temporal` 列,
|
||||
// 但四条列表读路径的 SELECT 与 scanItem 都不读它 —— 改扫描口径要动全部读点
|
||||
// (rules.go / store_list.go / tidy_store.go / preinsert_store.go,共 14→15 列),
|
||||
// 那是另一件事,不在本片授权范围。要按时间性位巡检/回填时直读该列即可。
|
||||
// 那是另一件事,不在本片授权范围。要按时间性位巡检/回填时直读该列即可;
|
||||
// 写入侧的校验/编码见 store_temporal.go。
|
||||
Temporal TemporalKind
|
||||
}
|
||||
|
||||
@@ -172,10 +173,6 @@ var (
|
||||
// 丢弃其中一半。
|
||||
ErrRetentionConflict = errors.New("memory: retention tier conflicts with expires_at")
|
||||
|
||||
// ErrInvalidTemporal 在条目的 Temporal(时间性元数据位,迁移 0027)不是闭集内
|
||||
// 的值时返回。空串**不在**此列:它是「尚未分型」这一合法状态。
|
||||
ErrInvalidTemporal = errors.New("memory: invalid temporal kind")
|
||||
|
||||
// ErrNotFound 在条目 id 不存在时返回。
|
||||
ErrNotFound = errors.New("memory: item not found")
|
||||
|
||||
@@ -305,9 +302,10 @@ func NewStore(ctx context.Context, db *sql.DB, log *slog.Logger) (*Store, error)
|
||||
// expires_at 之间的短窗口」在本片被结构性关闭(不再有第二笔 UPDATE)。
|
||||
//
|
||||
// 🆕 切片 M45:`item.Temporal`(时间性元数据位,迁移 0027)也随**这一笔** INSERT
|
||||
// 落下(`temporal` 列)。它的判定作用已经折算进上面的档位(temporal.go 的
|
||||
// temporalVerdict 是 resolveRetention 的第 4 步),列值本身是**留痕**:记录提取器
|
||||
// 当时标了什么,供事后巡检「模型答的是不是 one_off」—— 判定与留痕因此不会分叉。
|
||||
// 落下(`temporal` 列)。它的判定作用发生在同一次 `resolveRetention` 里(temporal.go
|
||||
// 的 temporalTier 出 long 结论 / judgeExtractionRetention 放开 N6 的位置判据,两处都
|
||||
// 在那一个函数内),列值本身是**留痕**:记录提取器当时标了什么,供事后巡检
|
||||
// 「模型答的是不是 one_off」—— 判定与留痕因此不会分叉。
|
||||
func (s *Store) Add(ctx context.Context, item MemoryItem) (MemoryItem, error) {
|
||||
if err := item.normalize(); err != nil {
|
||||
return MemoryItem{}, err
|
||||
@@ -345,7 +343,7 @@ func (s *Store) Add(ctx context.Context, item MemoryItem) (MemoryItem, error) {
|
||||
item.Content, string(tagsJSON), item.Confidence, item.SourceConversation,
|
||||
formatTime(item.CreatedAt), formatTime(item.UpdatedAt), item.DecayWeight,
|
||||
plan.insertStatus(), plan.absorbedID(), plan.expiresAt(), plan.retention(),
|
||||
string(item.Temporal))
|
||||
item.temporalColumn())
|
||||
if err != nil {
|
||||
return MemoryItem{}, fmt.Errorf("memory: insert item: %w", err)
|
||||
}
|
||||
@@ -453,12 +451,8 @@ func (it *MemoryItem) validate() error {
|
||||
if !it.RetentionTier.valid() {
|
||||
return fmt.Errorf("%w: %q", ErrInvalidRetentionTier, it.RetentionTier)
|
||||
}
|
||||
// 时间性位(迁移 0027)同样只接受闭集内的值:它是**枚举元数据**,一个自造的
|
||||
// 值("sometimes")既判不出档位、又不该静默落库 —— 提取器已经把模型输出归一
|
||||
// 到 unknown(temporal.go 的 normalizeTemporal),走到这里还是非法值只能是
|
||||
// 程序错误,直接拒。
|
||||
if !it.Temporal.valid() {
|
||||
return fmt.Errorf("%w: %q", ErrInvalidTemporal, it.Temporal)
|
||||
if err := it.validateTemporal(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user