P2-1(真缺口):r1 复看的变异 C 证明「在异步审查侧拿 item.Temporal 改判」当时
**整个 memory 包全绿** —— 已落库的行在异步侧被改判这条口径没有守卫,而异步侧
(applyVerdict / setIngestRetention)恰恰最容易被人随手接线。
新增 TestTemporalIsNotWiredIntoJudgement:扫生产代码(非 _test.go、非注释行)里
所有含 `.Temporal` 的行,逐行要求命中 temporalFieldSites 白名单(校验 3 条 / 产出 1 条,
**片段级**而不是文件级 —— 按文件放行会让同一文件里新写的接线路过),违规时报
「文件:行号: 表达式片段」;同时校验白名单每一条确实命中过,防止它老化成空头许可证。
变异实测(复看给的写法,插在 preinsert.go 的 applyVerdict 入口):
--- FAIL: TestTemporalIsNotWiredIntoJudgement
temporal 被接进了判定(裁决 D:它只许被记录,不许被判定读)…:
preinsert.go:476: if item.Temporal == TemporalOneOff {
删掉接线后全绿。
P2-2(文案与 D 矛盾):store_temporal.go 的归属清单原写「判定在 resolveRetention
(唯一入口,第 4 步)」—— D 之后第 4 步是 N1~N7、没有 temporal 步,那句等于给后来者
指了接线入口。改为:temporal 只被记录、不参与判定;要把 temporal 接进判定必须先拿到
模型真实标签分布,并只能通过 temporalFieldSites 白名单(有意的接线决定)进来。
另按复看提醒:temporal.go 头注里那两条统计 SQL 标明「**运维分析用,不属于读侧代码
路径**」,并写明读侧(timegate 与任何读侧 SQL)明令不许对 created_at/expires_at 用
datetime() —— 那是定宽文本比较,抄进查询层会静默判错。
回归:go build ./... + go vet ./... 全绿;memory/db 包测试全绿;memory -race 38.0s 全绿。
47 lines
2.3 KiB
Go
47 lines
2.3 KiB
Go
package memory
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
)
|
||
|
||
// 本文件是 `memory_items.temporal`(迁移 0027)在**写入路径**上的那一半:哨兵错误、
|
||
// 字段校验、以及落库时的列值编码。
|
||
//
|
||
// 🔴 其余三处的归属(别在这条链上另开一份实现):
|
||
// - **字段与语义**在 store.go 的 MemoryItem 上(结构体的字段不能跨文件声明),
|
||
// 闭集与归一化在 temporal.go;
|
||
// - **判定**仍是 `resolveRetention`(retention.go)里的 N1~N7 —— `temporal`
|
||
// **只被记录,不参与判定**(r1 复审裁决 D),因此 `resolveRetention` 里
|
||
// **没有、也不许有**「第 4 步看 temporal」这种东西;
|
||
// - **DDL** 在 migrations/0027 + store_schema.go + temporal_schema.go。
|
||
//
|
||
// 要把 `temporal` 接进判定,前置条件写在 temporal.go 头注:先拿到模型真实标签分布
|
||
// (那里给了两条只读 SQL),再改 `temporal_test.go` 的 temporalFieldSites 白名单 ——
|
||
// 那张表是**有意的接线决定**的唯一入口,不许绕过它。
|
||
//
|
||
// 拆出本文件只为守 AGENTS §10 的文件行数上限(store.go 已到 500 行)与「模块按职责
|
||
// 拆源文件」那条规范 —— 口径逐字未变。
|
||
|
||
// ErrInvalidTemporal 在条目的 Temporal(时间性元数据位,迁移 0027)不是闭集内的
|
||
// 值时返回。空串**不在**此列:它是「尚未分型」这一合法状态。
|
||
var ErrInvalidTemporal = errors.New("memory: invalid temporal kind")
|
||
|
||
// validateTemporal 只接受闭集内的值:时间性位是**枚举元数据**,一个自造的值
|
||
// ("sometimes")既判不出档位、又不该静默落库 —— 提取器已经把模型输出归一成
|
||
// unknown(temporal.go 的 normalizeTemporal),走到这里还是非法值只能是程序错误,
|
||
// 因此直接拒,而不是替它挑一个值。
|
||
func (it *MemoryItem) validateTemporal() error {
|
||
if !it.Temporal.valid() {
|
||
return fmt.Errorf("%w: %q", ErrInvalidTemporal, it.Temporal)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// temporalColumn 返回要写进 `temporal` 列的值(与 `ingestGatePlan.retention()` /
|
||
// `expiresAt()` 同族:把「条目 → 落库值」这一步收在类型自己身上,插入语句里不留
|
||
// 裸转换,也就不可能出现两处各转一次、口径分叉)。
|
||
func (it MemoryItem) temporalColumn() string {
|
||
return string(it.Temporal)
|
||
}
|