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

+19
View File
@@ -109,6 +109,10 @@ type Deps struct {
// Now 是决策/记录时间戳的墙钟来源。nil 则用
// time.Now,从而让依赖时间的行为在测试中可注入。
Now func() time.Time
// Audit 是自排审计出口(design/12 §N C.5)。它实现 Auditor 这个三字段窄接口,
// 生产接线注入网关(后端组装晚于 backthink,故也提供 SetAuditSink 晚绑定)。
// nil 则审计静默跳过:审计是留痕,不是门。
Audit Auditor
// Log 是基础日志器,来源标记为 "services/backthink"。
Log *slog.Logger
}
@@ -135,6 +139,10 @@ type Backthink struct {
// 生命周期锁分开,因此去重决策绝不会阻塞槽位换入。
eventMu sync.Mutex
lastEventKey string
// selfWake 是她自排一轮思考的成本闸与待跑槽(selfwake.go,design/12 §N C)。
// 它在 New 里按**已 normalize** 的配置构建(G3),Start 时由一个 worker 消费。
selfWake *selfWakeGate
}
// New 由 cfg 与 deps 构建 Backthink,套用默认值并校验
@@ -185,6 +193,9 @@ func New(cfg Config, deps Deps) (*Backthink, error) {
gate: gate,
now: now,
sessionID: sessionID,
// 闸只认**已 normalize** 的旋钮:未回落默认的 ≤0 会被滑窗读成「无规则」
// (恒放行且不记录),那就把「≤0 回落默认」静默变成「无上限」(G3)。
selfWake: newSelfWakeGate(cfg.SelfWakeLimit, cfg.SelfWakeWindow),
}, nil
}
@@ -206,6 +217,10 @@ func (b *Backthink) Start(ctx context.Context) {
b.cancel = cancel
b.wg.Add(1)
go b.tickLoop(cctx)
// 自排 worker(design/12 §N C.1):与心跳并列的第二个常驻 goroutine。
// Stop 的 cancel()+wg.Wait() 自动收走它,不引入新的生命周期状态。
b.wg.Add(1)
go b.selfWakeLoop(cctx)
b.mu.Unlock()
if b.deps.Bus != nil {
@@ -218,6 +233,10 @@ func (b *Backthink) Start(ctx context.Context) {
"quiet_interval_enabled", b.quietIntervalEnabled(),
"quiet_hours", quietHoursLabel(b.cfg.QuietStartHour, b.cfg.QuietEndHour),
"event_triggers", b.deps.Bus != nil,
// 自排的成本闸口径(design/12 §N C.5 的观测要求):与 quiet_interval 同族,
// 缺了它主人无法从日志确认「她一小时最多能叫醒自己几次」。
"self_wake_limit", b.cfg.SelfWakeLimit,
"self_wake_window", b.cfg.SelfWakeWindow.String(),
"session_id", b.sessionID)
}