feat(im): 主动消息「没送达微信」现在说得清(原因码 + 三处可见)
主人原话:「可以,这有必要,不然我老是收不到」。真机日志:
`backthink IM push failed … weixin_ilink: proactive push outside 24h window`
—— 她的话**送进了 Web 活跃客户端,但没送进微信**,原因是**平台规则**(微信 iLink
只允许用户最后一条消息后 24h 窗口内主动推送),不是崩溃。
本片只做「如实说清」,不绕规则:
- **原因码**:新增契约层分类件 `internal/channel/adapter/delivery.go`
(`DeliveryCategory` + 4 个码 + 可选自描述接口 `DeliveryFailure` / 读取函数),
**沿用**既有 voice `Category(err)` 与 `SystemNoticeNormalizer` 的范式,**不造第二套枚举**;
平台码由产生错误的层声明:`weixin_ilink` 的 `ErrProactiveWindow` 自报
`proactive_window_closed`(哨兵文本与 `errors.Is` 判定**逐字未变**)。
- **三处可见**:① 结构化 WARN(补齐 category/channel/platform/route);
② 审计 `backthink.push.im_undelivered`(与 `backthink.self_wake.*` 同族);
③ 推送结果 `Detail` 追加稳定标签 `im_undelivered=<渠道>:<原因码>` —— 面板**已在
渲染 Detail**(`Backthink.tsx:185`)⇒ 主人现在就能看到,**无需改前端**。
- **不丢消息**:IM 路失败照旧走活跃客户端路,且**只在 Web 真送达时** `pushed=true`;
有测试钉住「IM 失败不影响另一条路」(同一条消息没被丢)。
- **不做的**:不重试刷屏、不换渠道冒充微信、不伪造送达、不新增 `.env` 键。
验收(主会话复跑 + 亲自变异):**整树 `go build ./...` 通过**;4 个包 vet/测试全绿;
我用 -overlay 把 `ErrProactiveWindow` 退成裸 `errors.New` ⇒ weixin 包两条用例变红
(`category = "failed", want proactive_window_closed`),channelbridge/backthink 亦红。
⚠️ 未落地的建议(已登记,未硬改):给她的"推送回执"(`backthink_records` 加
`push_category`/`push_channel` 两列 + 下一轮注入只读一行)与面板结构化徽标 ——
红线:回执只能用来换渠道/换措辞/等窗口,**不得演变成绕平台规则**。
This commit is contained in:
1 parent
bff57bd1ae
commit
353293e807
9 files changed
+772
-31
No files matched your search
@@ -170,9 +170,6 @@ func New(cfg Config, deps Deps) (*Backthink, error) {
|
||||
gate = newRateGate(cfg.PushLimit, cfg.PushWindow, cfg.PushCooldown, cfg.KindWindow, log)
|
||||
}
|
||||
pusher := deps.Pusher
|
||||
if pusher == nil {
|
||||
pusher = newPusher(deps.Active, deps.IM, gate, log).withExporter(deps.Exporter)
|
||||
}
|
||||
|
||||
now := deps.Now
|
||||
if now == nil {
|
||||
@@ -183,7 +180,7 @@ func New(cfg Config, deps Deps) (*Backthink, error) {
|
||||
sessionID = defaultSessionID
|
||||
}
|
||||
|
||||
return &Backthink{
|
||||
b := &Backthink{
|
||||
cfg: cfg,
|
||||
deps: deps,
|
||||
log: log,
|
||||
@@ -195,7 +192,13 @@ func New(cfg Config, deps Deps) (*Backthink, error) {
|
||||
// 闸只认**已 normalize** 的旋钮:未回落默认的 ≤0 会被滑窗读成「无规则」
|
||||
// (恒放行且不记录),那就把「≤0 回落默认」静默变成「无上限」(G3)。
|
||||
selfWake: newSelfWakeGate(cfg.SelfWakeLimit, cfg.SelfWakeWindow),
|
||||
}, nil
|
||||
}
|
||||
// 未注入自定义推送器时,默认推送器在结构体构造**之后**组装:它要挂上
|
||||
// b.auditEvent(读取当前审计出口的回调),而该回调需要 b 本身。
|
||||
if pusher == nil {
|
||||
b.rebuildPusher()
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Start 启动后台工作:心跳定时器,以及(配置了 Bus 时)
|
||||
|
||||
Reference in new issue
Block a user