- selfwake.go:WakeNow + 待跑槽容量 1(先探槽合并不过闸、槽空才过闸、同锁)+ 闸 4 轮/小时
($DATA_DIR/config/backthink.json,零新 env 键)+ 审计四条 what + 启动日志两键 + 组合根接线
- G-A(主人已批):TriggerUnknown 作零值、既有两值编号后移(语义不动),未知触发走**最紧分支**
- 5 个变异自检(拆闸/≤0 未回落/过闸挪到探槽前=G1 失效形态/槽无界/零值挪走)
⚠️ 服务侧已就绪但**她仍调不到**(自排工具面属 C1-3)⇒ 不得写「C 已完成」
262 lines
13 KiB
Go
262 lines
13 KiB
Go
package backthink
|
||
|
||
import (
|
||
"fmt"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/bus"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/iot"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/urgency"
|
||
)
|
||
|
||
// TriggerKind 归类唤醒一轮思考的原因,好让推送路径套用
|
||
// 正确的策略:心跳受门控管辖(60min≤1 / 冷却 / 按风格),
|
||
// 事件是主人确实想听到的真实信号、绕过频率门。
|
||
type TriggerKind int
|
||
|
||
const (
|
||
// TriggerUnknown 是 TriggerKind 的**零值**:没有任何触发类别被声明。
|
||
//
|
||
// 它存在,是因为零值必须落在**最紧**的一档:此前零值等于 TriggerHeartbeat,
|
||
// 于是「漏填 Kind」静默变成「拿最宽的豁免」(长期静默段免检)——fail-open
|
||
// (design/12 §N C.4 的 G-A 修法,主人 2026-10-06 已批)。判定侧对未知触发
|
||
// **不豁免、不放行**(decide.go 的 activityHeld 让它落在最后那条扣下上),
|
||
// 推送侧也不把它当事件(照旧过 PushGate)。
|
||
//
|
||
// ⚠️ 它不是第四个真实触发:生产路径的每个 Trigger 都必须显式给出 Kind
|
||
// (heartbeatTrigger / startupTrigger / triggerForSubject / selfWakeTrigger
|
||
// 四处构造点,backthink.go 的 think 是唯一消费点)。
|
||
TriggerUnknown TriggerKind = iota
|
||
// TriggerHeartbeat 是周期性自主反思。它的主动推送受
|
||
// PushGate 限流,使主人绝不会被空洞的反思刷屏。
|
||
TriggerHeartbeat
|
||
// TriggerEvent 是真实事件(IoT 设备变化、提醒、与主人相关的
|
||
// 系统事件)。它的推送绕过 PushGate——"该发就发"——只受
|
||
// 同一事件去重约束。
|
||
TriggerEvent
|
||
// TriggerSelfWake 是她自己排起的一轮思考(design/12 §N C)。它既不是外部
|
||
// 事件、也不是周期性心跳:推送既不绕 PushGate,也不享长期静默段的豁免——
|
||
// 要求只是她说得出理由(见 decide.go 的 activityHeld)。
|
||
//
|
||
// 它是**追加**的第四个值:既有三个值的**标签与语义逐字不变**。G-A 修法让
|
||
// TriggerUnknown 占住零值,三个值因此各后移一位 —— 这只影响进程内的比较:
|
||
// Trigger.Kind 与 PushRequest.TriggerKind 都不落库、不上线(落库的是
|
||
// Record.Trigger = tr.Source 字符串),所以没有存量含义被改写。
|
||
TriggerSelfWake
|
||
)
|
||
|
||
// String 返回用于日志与记录的稳定标签。
|
||
func (k TriggerKind) String() string {
|
||
switch k {
|
||
case TriggerEvent:
|
||
return "event"
|
||
case TriggerSelfWake:
|
||
return "self_wake"
|
||
case TriggerHeartbeat:
|
||
return "heartbeat"
|
||
case TriggerUnknown:
|
||
return "unknown"
|
||
default:
|
||
return fmt.Sprintf("triggerkind(%d)", int(k))
|
||
}
|
||
}
|
||
|
||
// 本服务为事件驱动思考订阅的总线主题。它们是
|
||
// design/12 §1 定义的契约:一次 IoT 状态变化、一次提醒触发,
|
||
// 或一次泛化的系统事件。
|
||
const (
|
||
// SubjectReminderTrigger 是提醒触发所用的总线主题。
|
||
SubjectReminderTrigger = "reminder.trigger"
|
||
// SubjectSystemEvent 是泛化系统事件的总线主题。
|
||
SubjectSystemEvent = "system.event"
|
||
)
|
||
|
||
// SelfWakeSource 是她自我唤醒(自排一次思考)的稳定来源标签。它同时是
|
||
// Trigger.Source、Record.Trigger、面板标签与审计前缀的**唯一**真源——
|
||
// 各处都不许再写这个字面量(另写一份就会与落库值静默漂移)。
|
||
const SelfWakeSource = "self_wake"
|
||
|
||
// SelfWakeSummaryPrefix 是自排轮触发摘要的稳定前缀(`self_wake: <她写的理由>`)。
|
||
// context.go 靠它认出「这一轮是她自己排起的」并切到专属引导提示词,
|
||
// 与 StartupSummary 是同一族的稳定标识。它由 SelfWakeSource 派生,
|
||
// 因此标签改一处即两处同改。
|
||
const SelfWakeSummaryPrefix = SelfWakeSource + ": "
|
||
|
||
// SystemEvent 是调用方可发布到 SubjectSystemEvent 以传达
|
||
// 系统通知的可选类型化负载。Import 默认为 Normal。在
|
||
// design/12 §6 策略一之下,低价值的系统通知(加入/退出 ping、泛化的
|
||
// 平台噪声)被降级为仅登记一条记录,不唤醒
|
||
// 一整轮思考;只有高重要度(与主人相关)的系统事件才思考,
|
||
// 因此繁忙的公开平台无法触发昂贵反思的洪流。
|
||
//
|
||
// 它是「载荷自带重要度」这条通用通道的既有形式;生产者的紧急度若以
|
||
// urgency.Declarer 声明(IoT 的设备跃迁即如此),由 triggerForEvent 折算到
|
||
// 同一个 Trigger.Import 字段上,两条形式因此落到同一套判定。
|
||
type SystemEvent struct {
|
||
Summary string
|
||
Import Importance
|
||
}
|
||
|
||
// SelfWakeRequest 是她自己发起的一次唤醒请求(design/12 §N C.1)。它实现
|
||
// urgency.Declarer,因此她的声明与 IoT 设备跃迁走**同一条**折算通道
|
||
// (foldDeclared → escalateImport),判定侧不为它加任何分支。
|
||
type SelfWakeRequest struct {
|
||
// Reason 是她写给「下一轮自己」的理由。它是有界的:截断在工具面完成
|
||
// (design/12 §C.3),这里只承载。
|
||
Reason string
|
||
// Declared 是她声明的紧急度;零值 = LevelRoutine = 不上抬。
|
||
Declared urgency.Level
|
||
}
|
||
|
||
// Urgency 让 SelfWakeRequest 满足 urgency.Declarer(与 SystemEvent 的
|
||
// 载荷自带重要度并列,不新造第二条通道)。
|
||
func (r SelfWakeRequest) Urgency() urgency.Level { return r.Declared }
|
||
|
||
var _ urgency.Declarer = SelfWakeRequest{}
|
||
|
||
// Trigger 描述唤醒一轮思考的原因:来源子系统、
|
||
// 可读摘要与事件的估计重要度。
|
||
type Trigger struct {
|
||
Source string
|
||
Summary string
|
||
Import Importance
|
||
// Kind 记录本轮是周期性心跳还是真实事件,好让
|
||
// 推送路径能为真实事件绕过频率门(主人要求)。
|
||
Kind TriggerKind
|
||
}
|
||
|
||
// heartbeatTrigger 是周期性、低重要度的触发。低重要度意味着
|
||
// 该轮很少打扰用户,遵守 "默认不发送 chat 类型消息"。
|
||
func heartbeatTrigger() Trigger {
|
||
return Trigger{Source: "heartbeat", Summary: "periodic autonomous reflection", Import: ImportanceLow, Kind: TriggerHeartbeat}
|
||
}
|
||
|
||
// StartupSummary 是启动轮思考(裁决 07.4.4,design/12 §1)的触发摘要。它同时是
|
||
// context.go 里切换「拟人化启动消息」专属引导提示词的标识。
|
||
const StartupSummary = "process startup"
|
||
|
||
// startupTrigger 是进程启动后的第一轮思考(裁决 07.4.4):不等满一个心跳周期,
|
||
// 启动立即触发,并引导她以角色口吻产出一条拟人化启动问候(如「我回来啦」)。
|
||
// Kind 仍为 TriggerHeartbeat:启动轮的推送与心跳走完全相同的门与限频
|
||
// (PushGate / 打扰级联 / 活动门),进程反复重启也不会刷屏主人。
|
||
func startupTrigger() Trigger {
|
||
return Trigger{Source: "startup", Summary: StartupSummary, Import: ImportanceLow, Kind: TriggerHeartbeat}
|
||
}
|
||
|
||
// triggerForEvent 把总线事件映射为 Trigger:主题给出可读摘要与主题级默认重要度,
|
||
// 随后**通用通道**(foldDeclared)统一解出重要度 —— 任何事件载荷只要实现
|
||
// urgency.Declarer(IoT 的 DeviceUpdate 是第一个使用者),它声明的紧急度就被承认;
|
||
// 泛化系统事件沿用它既有的 SystemEvent.Import 口径。两条形式最终都落在同一个
|
||
// Trigger.Import 字段上,判定侧(decide.go)因此只认档位、不认生产者。
|
||
// 每个事件都打上 TriggerEvent,因此其主动推送绕过心跳的 PushGate。
|
||
func triggerForEvent(e bus.Event) Trigger { return foldDeclared(triggerForSubject(e), e.Payload) }
|
||
|
||
// foldDeclared 是「生产者声明的档位」叠到「主题级重要度」上的**唯一**折算点,
|
||
// 也是 escalateImport 的唯一调用入口;触发者是谁(总线事件 / 她自己)不影响形状。
|
||
// 自排请求经 selfWakeTrigger 走这里,因此两个声明点(工具参数与结论尾段)
|
||
// 落在同一个 Trigger.Import 上、且都只上抬。
|
||
func foldDeclared(tr Trigger, payload any) Trigger {
|
||
if d, ok := payload.(urgency.Declarer); ok {
|
||
tr.Import = escalateImport(tr.Import, d.Urgency())
|
||
}
|
||
return tr
|
||
}
|
||
|
||
// selfWakeTrigger 把一次自我唤醒请求折算成 Trigger(与事件同一条链)。
|
||
// 主题级默认是 ImportanceNormal(不是 Low):未声明档位时,安静时段与长期静默段
|
||
// 照常扣下;Low 会自造出一档「比例行还低」的新语义,没有必要。
|
||
func selfWakeTrigger(req SelfWakeRequest) Trigger {
|
||
return foldDeclared(Trigger{
|
||
Source: SelfWakeSource, Summary: selfWakeSummary(req.Reason),
|
||
Import: ImportanceNormal, Kind: TriggerSelfWake,
|
||
}, req)
|
||
}
|
||
|
||
// selfWakeSummary 用稳定前缀承载她写的理由(前缀见 SelfWakeSummaryPrefix)。
|
||
func selfWakeSummary(reason string) string { return SelfWakeSummaryPrefix + reason }
|
||
|
||
// triggerForSubject 给出各主题的可读摘要与主题级默认重要度(未声明紧急度的事件就
|
||
// 停在这一档)。它刻意不对任何生产者做专有判定:重要度已由通用通道统一解出。
|
||
func triggerForSubject(e bus.Event) Trigger {
|
||
switch e.Subject {
|
||
case iot.DeviceUpdateSubject:
|
||
if d, ok := e.Payload.(iot.DeviceUpdate); ok {
|
||
return Trigger{
|
||
Source: e.Subject,
|
||
Summary: fmt.Sprintf("device %s (%s) %s", d.Device.ID, d.Device.Type, d.Action),
|
||
Import: ImportanceNormal,
|
||
Kind: TriggerEvent,
|
||
}
|
||
}
|
||
return Trigger{Source: e.Subject, Summary: e.Subject, Import: ImportanceNormal, Kind: TriggerEvent}
|
||
case SubjectReminderTrigger:
|
||
return Trigger{Source: e.Subject, Summary: e.Subject, Import: ImportanceHigh, Kind: TriggerEvent}
|
||
case SubjectSystemEvent:
|
||
tr := Trigger{Source: e.Subject, Summary: e.Subject, Import: ImportanceNormal, Kind: TriggerEvent}
|
||
if sy, ok := e.Payload.(SystemEvent); ok {
|
||
if sy.Summary != "" {
|
||
tr.Summary = sy.Summary
|
||
}
|
||
tr.Import = sy.Import
|
||
}
|
||
return tr
|
||
default:
|
||
return Trigger{Source: e.Subject, Summary: e.Subject, Import: ImportanceNormal, Kind: TriggerEvent}
|
||
}
|
||
}
|
||
|
||
// importanceForUrgency 把生产者声明的紧急度折算为思考轮的重要度。它是通用通道与
|
||
// 判定侧之间**唯一**的换算点(design/12 §6 安全域豁免,裁决 1.8 / 07.4.2):零值
|
||
// Routine 折算 Normal —— 默认档永不获得穿透安静时段的资格;只有生产者显式声明
|
||
// critical 的安全域事件拿到 ImportanceCritical,从而穿透安静时段与工作 / 忙碌。
|
||
func importanceForUrgency(l urgency.Level) Importance {
|
||
switch l {
|
||
case urgency.LevelCritical:
|
||
return ImportanceCritical
|
||
case urgency.LevelWarning:
|
||
return ImportanceHigh
|
||
default:
|
||
return ImportanceNormal
|
||
}
|
||
}
|
||
|
||
// urgencyForImportance 是 importanceForUrgency 的**反向读法**:把一轮思考**实际生效**
|
||
// 的重要度还原成通用紧急度词汇,供存档携带精确档位(X2a;面板此前只能给出「下限」)。
|
||
//
|
||
// 它复用同一张既有对应关系(Critical↔critical、High↔warning),不引入任何新的门限或
|
||
// 折算规则:例行档对应 importanceForUrgency 的 Normal,而 ImportanceLow 只是「比例行
|
||
// 还安静」的心跳/启动轮 —— urgency 词汇里没有更低的档,故与 Normal 同归 routine
|
||
// (无损:routine 本就表示「安静时段照常扣下」这一类)。按档位高低比较而不是逐值
|
||
// 相等,是让将来插入更高档时它落在最高档、而不是静默掉回 routine。
|
||
//
|
||
// 取数点在 backthink.go 的 think:入参是该轮**实际生效**的重要度
|
||
// (escalatedImport 的结果,她自报的紧急度已折叠在内)。
|
||
func urgencyForImportance(i Importance) urgency.Level {
|
||
switch {
|
||
case i >= ImportanceCritical:
|
||
return urgency.LevelCritical
|
||
case i >= ImportanceHigh:
|
||
return urgency.LevelWarning
|
||
default:
|
||
return urgency.LevelRoutine
|
||
}
|
||
}
|
||
|
||
// escalateImport 把声明的紧急度叠到基础重要度上。声明只能**上抬**、绝不压低,因此
|
||
// 主题级默认(如提醒的 High)与安全域事件既得的档位不会被一句 routine 抹掉;
|
||
// 未声明(零值 LevelRoutine)时原样返回 base,没标紧急度的事件行为逐字不变。
|
||
//
|
||
// 两条来源共用它:① foldDeclared —— 生产者声明的档位,它是唯一折算点,服务总线事件
|
||
// (triggerForEvent)与她自排的一轮(selfWakeTrigger)两种触发;② backthink.go 的
|
||
// escalatedImport —— 她自己在主动思考轮结论里声明「我认为这条紧急」。因此「工具参数
|
||
// urgency」与「结论尾段 [紧急:…]」两个声明点落在同一个 Trigger.Import 上,
|
||
// 该轮生效值取两者的较高档。
|
||
func escalateImport(base Importance, declared urgency.Level) Importance {
|
||
if declared == urgency.LevelRoutine {
|
||
return base
|
||
}
|
||
if up := importanceForUrgency(declared); up > base {
|
||
return up
|
||
}
|
||
return base
|
||
}
|