裁定(已发安卓):**能修**,走**写入侧规整** —— 不指望模型输出、不动客户端。 - 新增 internal/runtime/turnreply/paragraph.go 的 NormalizeParagraphs: 把未被 <p> 包裹的段落补上 <p>(按空行分段;<action>/<narration> 段也包进 <p>, 与正确形态 <p><action>…</action>…</p> 一致) - **幂等**守门断言:已完全包裹的文本逐字不变(防二次加工) - 落点覆盖 Web 与 IM 两条落库路径(gateway/session_turn_reply.go、channelbridge/turnreply.go、 proactive.go)⇒ 两边共用同一份规整文本 ⇒ **历史一次性变好、两个客户端零改动**、渲染口径不分叉 (测试与变异详见切片报告)
主人 2026-10-03 拍板「注释一律中文」。本批 = WS 上行门与握手回放那批文件: ws_hub.go / ws_history.go / ws_route.go / ws_role_gate_test.go / voice_signal_test.go 含中文的整行注释 6 → 148;无行尾注释。 校验(判据 = "只改注释、零代码字节改动"): - git diff -U0 | 排除 +++/--- / 注释行 / 空行 / 行尾注释 → 为空 (含"把纯空行变动也视为违规"的更严变体,同样为空) - 独立实现:两侧剥掉注释后逐行比对代码文本 → 5/5 全等 - gofmt -l 干净(改动前同样干净,不存在"顺手格式化") - go test ./... 全绿;go build/vet 绿;linux/amd64 + darwin/arm64 交叉编译绿 安全约束强度逐句保留:ws_route.go 的「默认拒(非黑名单)/ 删掉它等于放开一切」、 ws_history.go 的「门在 replayHistory 第一条语句 / 扩角色须先登记 design/11 §7.2 / 回放不含 thinking」、ws_role_gate_test.go 的测试名与断言字面量一字符未改。
主人 2026-10-03 拍板"这一类要收紧"后,补齐 §7.1 下行门之外的两处同类缺口: viewer 是只读监控档,却能通过 WS 拿到内容、甚至发起写入。 A. 握手 history 回放仅 owner/admin(ws_history.go) 连接建立时回放主会话最近 50 条消息正文,此前对任意角色照发 ⇒ viewer 一连上就 拿走主人对话内容。门放在 hub.replayHistory 的第一行 —— 它是唯一发出 history 帧的 函数,将来多一个调用者也绕不过去。 B. 上行 JSON 帧按角色授权(ws_route.go,fail-closed) 此前 route 完全没有角色检查。现在 route 的第一条语句即:非 owner/admin → 回定向 {type:"error",data:{code:"forbidden"}} 并 return,因此"不广播、不驱动模型、不落库、 不返回任何内容"是结构性的,而不是每个 handler 各自记得检查。写成默认拒而非黑名单 —— chat / interrupt / subagent_ask / task_output_query / voice_input / mode_switch 以及 将来任何新帧都自动被拒。协议级帧(ping/pong/close)由 gorilla 在 ReadMessage 内处理, 不受影响(已实测:被拒后 ping→pong 正常、close 后服务端注销连接)。 被修掉的真实越权:viewer 发 chat 会真的驱动模型并把消息落库;subagent_ask / task_output_query 的定向应答会绕过 §7.1 下行门,把子代理上下文/任务输出回给 viewer。 契约:Docs/api-client.md → v0.49(对 viewer 收紧;owner/admin 逐帧形状不变) 验证:go build/vet 绿;go test ./... = 58 包 ok / 0 FAIL;-race ./internal/services/gateway/... ok;linux/amd64 + darwin/arm64 交叉编译绿;新测试 -count=3 稳定。 真 httptest WS + 真 JWT 逐帧实测:viewer 收不到 history 回放、上行一律定向 forbidden 且 runner 计数 = 0 / 落库 = 0;owner 侧 chat 驱动 runner = 1 且落库 2 条。 变异验证:① 去掉 A 的门 → viewer 收到 {"type":"history",...,"content":"主人的私密提问"},FAIL; ② 去掉 B 的门 → viewer 帧驱动 runner 1 次并落库 2 条、且直接收到 subagent_event / task_output, FAIL。均已还原;且撤掉 B 时 A 的测试仍独立 PASS(两把门不互相代偿)。 遗留(如实登记):非 JSON 的上行字节不经过 route 的角色门(零副作用:不带 type、不触发 handler、不落库);admin 上行未单独测一格(与 owner 共用同一分支)。