feat(backthink): 自排 C1-2 本体 + TriggerUnknown 修 fail-open(V1)

- selfwake.go:WakeNow + 待跑槽容量 1(先探槽合并不过闸、槽空才过闸、同锁)+ 闸 4 轮/小时
  ($DATA_DIR/config/backthink.json,零新 env 键)+ 审计四条 what + 启动日志两键 + 组合根接线
- G-A(主人已批):TriggerUnknown 作零值、既有两值编号后移(语义不动),未知触发走**最紧分支**
- 5 个变异自检(拆闸/≤0 未回落/过闸挪到探槽前=G1 失效形态/槽无界/零值挪走)
⚠️ 服务侧已就绪但**她仍调不到**(自排工具面属 C1-3)⇒ 不得写「C 已完成」
This commit is contained in:
AskaEth committed 2026-10-06 20:25:04 +08:00
1 parent 301f8b4276
commit ed5005307a
12 files changed
+1193 -8

No files matched your search

@@ -286,3 +286,59 @@ func TestDecide_ActivityGate_SelfWakeNeedsDeclaredUrgency(t *testing.T) {
t.Fatalf("long idle heartbeat should still push (07.4.3), got %+v", got)
}
}
// TestTriggerKind_ZeroValueIsUnknownAndTight 是 G-A 修法(design/12 §C.4 的零值语义,
// 主人 2026-10-06 已批)的探针:`TriggerKind` 的零值必须是 **TriggerUnknown**,且它
// 在判定侧走**最紧**分支 —— 长期静默段里不豁免(`{Unknown, Low/Normal}` 扣下)、
// 推送侧不被当成事件(照旧过 PushGate)。
//
// 它守的是 fail-open 的方向:改动前零值 = TriggerHeartbeat,「漏填 Kind」静默变成
// 「拿最宽的豁免」。变异:把 TriggerUnknown 挪出零值(如让 TriggerHeartbeat 回到 0),
// 或把零值分支混进心跳豁免 ⇒ 本用例变红。
func TestTriggerKind_ZeroValueIsUnknownAndTight(t *testing.T) {
t.Parallel()
var zero TriggerKind
if zero != TriggerUnknown {
t.Fatalf("TriggerKind 的零值 = %v(%d),want TriggerUnknown", zero, int(zero))
}
if TriggerUnknown != 0 {
t.Fatalf("TriggerUnknown = %d, want 0(零值必须是最紧的那一档)", int(TriggerUnknown))
}
if got := TriggerUnknown.String(); got != "unknown" {
t.Fatalf("TriggerUnknown.String() = %q, want %q", got, "unknown")
}
// 既有三个值的标签一个字都不许变(落库/面板/审计都以它们为真源)。
for kind, want := range map[TriggerKind]string{
TriggerHeartbeat: "heartbeat",
TriggerEvent: "event",
TriggerSelfWake: SelfWakeSource,
} {
if got := kind.String(); got != want {
t.Fatalf("TriggerKind(%d).String() = %q, want %q", int(kind), got, want)
}
}
b := testBackthink()
longIdle := DecisionInput{
Trigger: "unknown.source",
At: dayAt(),
UserState: UserState{Boredom: 0.9},
LastActivity: dayAt().Add(-3 * time.Hour),
HasActivity: true,
ConclusionPush: true,
Kind: TriggerUnknown, // 零值:漏填 Kind 的等价形态
}
for _, imp := range []Importance{ImportanceLow, ImportanceNormal} {
in := longIdle
in.Import = imp
if got := b.Decide(in); got.ShouldPush {
t.Fatalf("未知触发(Import=%v)在长期静默段必须扣下,got %+v", imp, got)
}
}
// 高重要度仍按「值得说」的既有规则放行:G-A 收的是**豁免**,不是把未知一律封死。
high := longIdle
high.Import = ImportanceHigh
if got := b.Decide(high); !got.ShouldPush {
t.Fatalf("未知触发但重要度高(>= High)应放行,got %+v", got)
}
}