feat(backthink): 静默期自排 C 的判定侧(C1-1,切片三之一)
- 追加 TriggerSelfWake(不动既有编号)+ SelfWakeRequest(走既有 urgency 声明通道,不新造第二条) - escalateImport 折算收成唯一 foldDeclared(事件轮与自排轮共用、只上抬取较高档) - DecisionInput.Heartbeat bool → Kind TriggerKind;心跳仍免检、**自排不享心跳豁免**(要求声明 ≥ warning) - push 与档位分离:push.go 零改动即自动正确(eventDriven 对 self_wake 为 false ⇒ 照旧过 PushGate) - 4 个变异自检(折算空操作/删自排分支/常量传心跳/非事件一律豁免) ⚠️ 本片只是判定侧:WakeNow/worker/闸属 C1-2,工具面与提示词属 C1-3 ⇒ **不得写『C 已完成』** ⚠️ 遗留 G-A(建议登记进 design/12 C.4):TriggerKind 零值=TriggerHeartbeat ⇒ 漏填=最宽豁免(fail-open); 本片已在字段注释与单测里显式化,但规格侧应补一句或引入 TriggerUnknown
This commit is contained in:
1 parent
ea43f07bce
commit
94da6bcd6d
8 files changed
+353
-26
No files matched your search
@@ -0,0 +1,149 @@
|
||||
package backthink
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/history"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/urgency"
|
||||
)
|
||||
|
||||
// 本文件守住 design/12 §N C 的**判定侧**(切片 C1-1):自我唤醒是追加的第三个
|
||||
// TriggerKind,它的档位折算与总线事件共用同一条通道(foldDeclared → escalateImport),
|
||||
// 判定侧只多认一个 kind、不加第二条档位通道。对应 C.8 判据 1 / 4 / 6 / 9。
|
||||
//
|
||||
// ⚠️ 本片**不产生**自排轮:WakeNow / worker / 工具面分别属 C1-2 / C1-3,因此这里的
|
||||
// 「生产路径」用例直接构造 selfWakeTrigger —— 它证明的是链路已就绪,不是能力已交付。
|
||||
|
||||
// TestTriggerKind_SelfWakeAppendedWithStableLabels 钉住标签与编号口径(C.6 落地清单):
|
||||
// 落库值(Record.Trigger)、面板值与审计前缀共用同一个真源;新 kind 是**追加**的,
|
||||
// 既有两个值的编号与标签逐字不变(改编号会静默改写已落库记录的含义)。
|
||||
func TestTriggerKind_SelfWakeAppendedWithStableLabels(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := []struct {
|
||||
kind TriggerKind
|
||||
want string
|
||||
}{
|
||||
{TriggerHeartbeat, "heartbeat"},
|
||||
{TriggerEvent, "event"},
|
||||
{TriggerSelfWake, SelfWakeSource},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
if got := tc.kind.String(); got != tc.want {
|
||||
t.Fatalf("TriggerKind(%d).String() = %q, want %q", int(tc.kind), got, tc.want)
|
||||
}
|
||||
}
|
||||
if !(TriggerHeartbeat < TriggerEvent && TriggerEvent < TriggerSelfWake) {
|
||||
t.Fatalf("kind 编号不再是追加的:heartbeat=%d event=%d self_wake=%d",
|
||||
int(TriggerHeartbeat), int(TriggerEvent), int(TriggerSelfWake))
|
||||
}
|
||||
if SelfWakeSource != "self_wake" {
|
||||
t.Fatalf("SelfWakeSource = %q, want %q(面板与文档按此值登记)", SelfWakeSource, "self_wake")
|
||||
}
|
||||
if SelfWakeSummaryPrefix != SelfWakeSource+": " {
|
||||
t.Fatalf("SelfWakeSummaryPrefix = %q, want %q", SelfWakeSummaryPrefix, SelfWakeSource+": ")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSelfWakeTrigger_ShapeAndUndeclaredDefault 钉住自排轮的形状(C.1/C.2):来源与
|
||||
// 摘要用稳定标签、主题级默认是 Normal(不是 Low —— 不新造一档「比例行还低」的语义)、
|
||||
// Kind 是 TriggerSelfWake;零值声明不上抬。
|
||||
func TestSelfWakeTrigger_ShapeAndUndeclaredDefault(t *testing.T) {
|
||||
t.Parallel()
|
||||
tr := selfWakeTrigger(SelfWakeRequest{Reason: "想看看她今天的安排"})
|
||||
if tr.Source != SelfWakeSource {
|
||||
t.Fatalf("Source = %q, want %q", tr.Source, SelfWakeSource)
|
||||
}
|
||||
if tr.Kind != TriggerSelfWake {
|
||||
t.Fatalf("Kind = %v, want %v", tr.Kind, TriggerSelfWake)
|
||||
}
|
||||
if tr.Import != ImportanceNormal {
|
||||
t.Fatalf("Import = %v, want %v(未声明档位时不上抬)", tr.Import, ImportanceNormal)
|
||||
}
|
||||
if tr.Summary != SelfWakeSummaryPrefix+"想看看她今天的安排" {
|
||||
t.Fatalf("Summary = %q, want the stable prefix + 她写的理由", tr.Summary)
|
||||
}
|
||||
// 理由为空也要带前缀:context.go 靠前缀认轮次,不靠理由内容。
|
||||
if empty := selfWakeTrigger(SelfWakeRequest{}); !strings.HasPrefix(empty.Summary, SelfWakeSummaryPrefix) {
|
||||
t.Fatalf("empty-reason Summary = %q, want the stable prefix", empty.Summary)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSelfWakeRequest_DeclarerChannel 钉住自排**没有**第二条档位通道:她的声明经
|
||||
// urgency.Declarer 被 foldDeclared 读出(与 IoT 设备跃迁同一个接口),因此
|
||||
// SelfWakeRequest 必须满足该接口。
|
||||
func TestSelfWakeRequest_DeclarerChannel(t *testing.T) {
|
||||
t.Parallel()
|
||||
var d urgency.Declarer = SelfWakeRequest{Reason: "r", Declared: urgency.LevelWarning}
|
||||
if got := d.Urgency(); got != urgency.LevelWarning {
|
||||
t.Fatalf("Urgency() = %v, want %v", got, urgency.LevelWarning)
|
||||
}
|
||||
if got := (SelfWakeRequest{}).Urgency(); got != urgency.LevelRoutine {
|
||||
t.Fatalf("zero-value Urgency() = %v, want %v(未声明 = routine)", got, urgency.LevelRoutine)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBackthink_SelfWakeRoundProductionPath 走生产路径 think():一轮自排思考落库为
|
||||
// trigger="self_wake",长期静默段里例常自排被扣下(C.4),而声明 warning 后放行且
|
||||
// 落库档位是**折算后**的 warning(C.6)。对应 C.8 判据 6 / 9。
|
||||
//
|
||||
// 变异:把 backthink.go 的 `Kind: tr.Kind` 改回传入常量(如 TriggerHeartbeat)⇒ 第一段
|
||||
// 的「例常自排被扣下」变红;把 SelfWakeSource 改回心跳值 ⇒ 落库断言变红。
|
||||
func TestBackthink_SelfWakeRoundProductionPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
pusher := &mockPusher{result: PushResult{Pushed: true, Route: RouteActiveClient}}
|
||||
b, _ := newBackthink(t, Config{}, Deps{
|
||||
Thinker: &mockThinker{text: "[我决定:push]\n我想再看看她今天的安排"},
|
||||
State: &mockState{state: UserState{}},
|
||||
Pusher: pusher,
|
||||
Recent: &fakeRecent{msgs: []history.Message{{
|
||||
Role: history.RoleUser,
|
||||
Content: "我先去忙了",
|
||||
CreatedAt: dayAt().Add(-3 * time.Hour).UnixMilli(),
|
||||
}}},
|
||||
Now: func() time.Time { return dayAt() },
|
||||
})
|
||||
|
||||
b.think(context.Background(), selfWakeTrigger(SelfWakeRequest{Reason: "复盘一下今天的对话"}))
|
||||
|
||||
if got := pusher.count(); got != 0 {
|
||||
t.Fatalf("pusher calls = %d, want 0(长期静默段里例常自排被扣下)", got)
|
||||
}
|
||||
recs, err := b.List(context.Background(), 10)
|
||||
if err != nil {
|
||||
t.Fatalf("List: %v", err)
|
||||
}
|
||||
if len(recs) != 1 {
|
||||
t.Fatalf("records = %+v, want exactly one self-wake record", recs)
|
||||
}
|
||||
if recs[0].Trigger != SelfWakeSource {
|
||||
t.Fatalf("Record.Trigger = %q, want %q", recs[0].Trigger, SelfWakeSource)
|
||||
}
|
||||
if recs[0].ShouldPush {
|
||||
t.Fatalf("record = %+v, want the routine self wake held", recs[0])
|
||||
}
|
||||
if !strings.Contains(recs[0].PushResult.Detail, "self-initiated wake") {
|
||||
t.Fatalf("PushResult.Detail = %q, want it to name the self-initiated wake hold", recs[0].PushResult.Detail)
|
||||
}
|
||||
|
||||
// 同一轮只换声明:warning ⇒ 折算 High ⇒ 长期静默段放行,且落库档位是折算后的值。
|
||||
b.think(context.Background(), selfWakeTrigger(SelfWakeRequest{
|
||||
Reason: "她可能正需要人陪", Declared: urgency.LevelWarning,
|
||||
}))
|
||||
|
||||
recs, err = b.List(context.Background(), 10)
|
||||
if err != nil {
|
||||
t.Fatalf("List: %v", err)
|
||||
}
|
||||
if !recs[0].ShouldPush || !recs[0].PushResult.Pushed {
|
||||
t.Fatalf("declared self wake = %+v, want a delivered push", recs[0])
|
||||
}
|
||||
if recs[0].Urgency != urgency.LevelWarning.String() {
|
||||
t.Fatalf("Record.Urgency = %q, want %q(落库的是该轮实际生效的档位)", recs[0].Urgency, urgency.LevelWarning.String())
|
||||
}
|
||||
if got := pusher.count(); got != 1 {
|
||||
t.Fatalf("pusher calls = %d, want 1", got)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user