Commit Graph
4 Commits
Author SHA1 Message Date
AskaEth 1ae9158250 test(channelbridge): 窗口夹具按截止时刻推钟 + 去掉无法证实的替身锁(M33 r1 复审)
P1-1(channelbridge 的 -race 门仍红,红在用例不确定性而不是竞态):
- coalesceTimers 改成**截止时刻**语义:每次 after(d) 记 at = 注入时钟 + d,
  推钟只把时钟推进到「当前最早的截止时刻」再触发它。旧写法按 FIFO 逐个触发并
  按请求时长累加,实测 12 条消息内攒到 5 个废弃定时器把时钟推 +10s(窗口 2s、
  绝对上限 6s)⇒ 循环头走「到点直接发车、不 takeAll」的分支,队列尾部落到下一批。
- 推钟前强制「车道排空」这条不变量(pump/advanceAndFire/waitIdle):
  时钟只表示时间过去,它一转就可能跳过 takeAll,因此先等 worker 把货收进本批。
- sendSettled:投递后按**序号**等「这次投递之后新挂的定时器」(旧的都已被 worker
  放弃、再没人监听),供「投递与推钟交替」的用例(每 500ms 一条的绝对上限用例)。
- fireLatest/advanceAndFire 带 done 谓词重试,避免「登记与触发之间续臂」导致
  推了钟却没人醒;waitIdle 一边等一边推窗口(worker 停在窗口里时永远走不到退休)。
- 用例侧只补相位与等待,不动断言:TwelveBurst 读历史前等 assistant 行落库;
  准入用例断言「最新一次续臂」(原来按下标取 d[1],隐含臂数假设);
  stale 用例两条都投完再推时钟(车道不会在两条之间退休)。

P2-1(替身锁无法用变异证实)选 A:删掉 stubChatter/stubSessions 的锁与访问器,
退回原字段访问,并写清理由 —— 21 个用例全走同步便捷路径
(b.process → processFrom → runBatch → Loop.Run),DEV HEAD 上 -race 数十次跑
(含把锁删掉的变异)DATA RACE 恒为 0;若将来从车道 worker 触碰这些字段必须加锁
并补 -race 用例。

证据:go test -race -count=3 ./internal/services/channelbridge/ 连跑 6 轮
18/18 全绿、DATA RACE=0;coalesce 子集 -count=80 全绿;整包 -race -count=20 全绿;
全树 go build/vet/test -count=1 全绿。
2026-10-24 23:18:45 +08:00
AskaEth 7d9b2132bb test(testhealth): 替身竞态加锁 + 墙钟断言硬度(M33)
四处测试健康债务,只改 *_test.go,不动生产代码、不降并发:

- channelbridge:stubChatter / stubSessions 的跨 goroutine 字段一律经 mu 访问
  (Run/GetOrCreate 在车道 worker 上写、测试 goroutine 读,两者无 happens-before;
  负向断言「等一会儿再看循环有没有被驱动」尤其如此)。读写走访问器,Messages()
  返回副本;t.Parallel 与并发度不变。
- plugins/tools/io:claimProbeTimeout 2s(真实时间预算)→ 60s,并由 claimProbeWait
  按 t.Deadline 收缩。探针只等 goroutine 被调度(无时间语义),放大余量把
  「机器快慢」从断言里去掉,死锁仍以断言失败暴露。
- backthink:TestSelfWakeGate_WindowExpiry 窗口 120ms → 6s(给 waitFor 的 4s 上界
  留 2s 余量),到期等待改为按首次受理时刻推算 window+250ms,不再盲睡 3×窗口。
  另修 TestSelfWakeStatus_MatchesReceiptAndTracksPendingSlot:worker 先 take 再进
  Think,卡 Think 并不占待跑槽 —— 改为「上一轮把 worker 占住再断言 Pending」。
- memory:墙钟实测用例显式给闸 5s 预算(measureGateBudget),不再因默认 150ms
  超时 fail-open 打炸「每轮都真扫真判」前置断言;并把 GateFailed 纳入该断言。
2026-10-24 22:24:14 +08:00
AskaEth 91e4dd2fe1 fix(channel): 静默登记同源第三方标注 —— 无昵称非主人不再被读成主人
review p1:回帖路径已改用 thirdPartySenderName(非主人恒有标注:昵称 → actor
uid → 「未署名发送者」),但 registerSilenced 仍用 senderDisplayName,非主人
无昵称时写出空串;静默行会被 transcript 读回她的上下文,而受限会话里空标注就是
「主人本人」⇒ 在 senderName 可选且无兜底的渠道(webhook)上,一条没昵称的陌生人
消息被渲染成「[发言归属] 以下发言来自主人(管理员账户)本人」。

- light.go:270:senderDisplayName → thirdPartySenderName,与回帖路径同源。
  senderDisplayName 未成死代码:仍被 light.go:223(LightJudge 输入)、
  engage.go:284、flowgate.go:183 使用。
- 新增 TestSilencedNamelessMemberIsNotReadAsTheOwner:走真链路(静默登记 → 落库
  → 下一回合播种转录 → loop 归属渲染 → provider 请求),钉住「无昵称的非主人经
  静默登记后,读回上下文时不得出现主人标注」。修前必红(写入口径与模型侧两条
  断言同时报,含被读成主人的原样请求),修后绿。

验证:go build ./... / go vet ./... / go test -count=1 ./...(全树)全绿。
2026-10-13 20:34:04 +08:00
AskaEth 8ab241f627 feat(channel): 群聊发言人归属落进模型上下文(user 侧也产出标注)
裁决 06.4.1 行动项①②落地(主人 2026-10-13 定稿:结构化归属 + system 标注,
不把元数据拼进用户正文)。

- loop/attribution.go:发言人归属从「只认 assistant」扩到 user 侧,按三条
  结构事实各出一条边界说明 —— assistant 的角色(她自己 / 别的角色,措辞逐字
  不变)、受限会话里带昵称的第三方人员(明标「不是主人」)、以及不带昵称的
  主人本人;受限会话里未标注的 assistant 历史按 design/22 §7-4 归到本会话角色。
  标注仍只新增 system 消息,正文一字不改。
- channelbridge/inbound_speaker.go(自 inbound.go 拆出):第三方发送者标注车道
  thirdPartySenderName —— 主人恒返回空(主人从不被标注),其余昵称缺失时退到
  平台 uid 再退到「未署名发送者」,绝不落进「空标注 = 主人」那条车道;
  thirdPartyBoundaryPrompt 删掉「只有未标注昵称的消息才是管理员(主人)发言」,
  改为「谁在说话以「发言归属」标注为准」。
- channelbridge/source.go:来源行删掉「(非管理员账户=第三方人员,未标注昵称=主人)」。
- 主人本人在群里的消息落库不再写昵称(sender_name 空 = 主人),使转录里同一句话
  的归属与当回合一致。

验证:go build ./... / go vet ./... / go test ./...(含全树)全绿;
变异自证:把 user 侧归属恢复成不产出 ⇒ 3 条 loop 用例 + 1 条渠道端到端用例变红。
2026-10-13 20:24:21 +08:00