docs(gateway): gateway 第 2 批 5 个文件注释改中文(只改注释,代码零字节改动)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

主人 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 的测试名与断言字面量一字符未改。
This commit is contained in:
AskaEth committed 2026-10-03 22:10:33 +08:00
1 parent fe1ff33115
commit 355f81d04a
5 files changed
+149 -181

No files matched your search

+27 -32
View File
@@ -14,8 +14,7 @@ import (
"git.yeij.top/AskaEth/Cyrene_v2/internal/bus"
)
// WebSocket tuning constants. The ping/pong cadence keeps intermediary proxies
// from dropping idle connections.
// WebSocket 调参常量。ping/pong 节奏用于防止中间代理把空闲连接断开。
const (
wsEventsPattern = "ws.>"
wsPrefix = "ws."
@@ -30,9 +29,8 @@ const (
wsSendBuffer = 32
)
// wsEvent is the outbound (server→client) frame. TurnID/Seq are optional and
// used by the streaming path so a client can correlate a burst of streamed
// events to one assistant turn and keep them in order.
// wsEvent 是下行(服务端→客户端)帧。TurnID/Seq 可选,由流式路径使用,
// 让客户端能把一串流式事件归到同一个助手回合并保持顺序。
type wsEvent struct {
Type string `json:"type"`
TurnID string `json:"turnId,omitempty"`
@@ -41,15 +39,15 @@ type wsEvent struct {
Data any `json:"data,omitempty"`
}
// wsInbound is the client→server frame. Data carries the per-type payload.
// wsInbound 是上行(客户端→服务端)帧。Data 承载按类型区分的载荷。
type wsInbound struct {
Type string `json:"type"`
ReqID string `json:"reqId,omitempty"`
Data json.RawMessage `json:"data,omitempty"`
}
// hub owns the connection set and relays bus events to clients, each frame
// gated by its type (see broadcast / ws_frame_scope.go).
// hub 持有连接集合并把 bus 事件转发给客户端,每个帧按其类型过门
// (见 broadcast / ws_frame_scope.go)。
type hub struct {
mu sync.RWMutex
conns map[*wsConn]struct{}
@@ -61,8 +59,8 @@ type hub struct {
streaming bool
orchestrator *Orchestrator
history HistoryStore
// envResolver resolves each turn's role (design/22 §4.5); it mirrors the
// server's resolver so the WS path and the REST path never diverge.
// envResolver 解析每个回合的角色(design/22 §4.5);它与 server 的解析器
// 同源,使 WS 路径与 REST 路径不会分叉。
envResolver turnEnvResolver
log *slog.Logger
upgrader websocket.Upgrader
@@ -72,7 +70,7 @@ type hub struct {
streamSeq atomic.Uint64
}
// newHub builds a hub bound to the server's dependencies.
// newHub 构建一个绑定到 server 依赖的 hub。
func newHub(s *Server) *hub {
origins, allowAll := s.allowedOrigins()
return &hub{
@@ -96,10 +94,9 @@ func newHub(s *Server) *hub {
}
}
// start subscribes the hub to the bus event stream. Publishing to ws.> arrives
// here and is fanned out to clients; the producer bridge patterns (iot/task/
// automation/reminder/subagent-progress) are also subscribed and normalized into
// ws.* event types. Nothing happens when no bus is wired.
// start 让 hub 订阅 bus 事件流。发往 ws.> 的事件到达此处后广播给客户端;
// 生产者桥接模式(iot/task/automation/reminder/subagent-progress)也一并订阅,
// 并归一为 ws.* 事件类型。未接入 bus 时什么都不做。
func (h *hub) start() {
if h.bus == nil {
return
@@ -114,8 +111,8 @@ func (h *hub) start() {
}
}
// close cancels the bus subscription, interrupts every orchestrator session (so
// no background turn outlives the hub), and shuts every live connection down.
// close 取消 bus 订阅、中断所有 orchestrator 会话(使后台回合不会比 hub 活得
// 更久),并关闭每条在线连接。
func (h *hub) close() {
if h.cancel != nil {
h.cancel()
@@ -135,14 +132,14 @@ func (h *hub) close() {
}
}
// register adds a connection to the set.
// register 把一条连接加入集合。
func (h *hub) register(c *wsConn) {
h.mu.Lock()
h.conns[c] = struct{}{}
h.mu.Unlock()
}
// unregister removes a connection from the set.
// unregister 把一条连接从集合中移除。
func (h *hub) unregister(c *wsConn) {
h.mu.Lock()
if _, ok := h.conns[c]; ok {
@@ -151,24 +148,22 @@ func (h *hub) unregister(c *wsConn) {
h.mu.Unlock()
}
// connCount reports the number of live connections (observability/tests).
// connCount 返回在线连接数(可观测性/测试)。
func (h *hub) connCount() int {
h.mu.RLock()
defer h.mu.RUnlock()
return len(h.conns)
}
// broadcast is the single delivery entry for outbound frames, and the design/11
// §7.1 role gate itself: it looks the frame's type up (frameReachesAll) and fans
// a registered 「状态机信号」 out to every registered connection, while every other
// type — content, tool, persisted-message, graph, private-state, and any type not
// classified yet — reaches owner + admin only. Registration happens only after the
// JWT is verified (ws_hub_conn.go), so the connection set is the authenticated set.
// broadcast 是下行帧的唯一投递入口,也是 design/11 §7.1 角色门本身:它按帧的
// 类型查表(frameReachesAll),把登记在册的「状态机信号」广播给每条已注册连接,
// 而其余类型 —— 内容、工具、持久化消息、图谱、角色私有状态,以及任何尚未分类
// 的类型 —— 只送达 owner + admin。连接只在 JWT 校验通过后才注册
// (ws_hub_conn.go),因此连接集合就是已认证集合。
//
// Gating here rather than at the call sites is deliberate: there is exactly one
// fan-out loop, so a frame cannot be sent to the wrong audience by a call site that
// forgot to pick the restricted entry. It never blocks on any single (possibly
// slow) client: a full send buffer drops the frame.
// 把门放在这里而非调用点是刻意的:全包只有这一个 fan-out 循环,调用点即使忘了
// 改走受限入口,也不可能把帧投错受众。它也不会被任何单个(可能很慢的)客户端
// 阻塞:发送缓冲满即丢弃该帧。
func (h *hub) broadcast(ev wsEvent) {
b, err := json.Marshal(ev)
if err != nil {
@@ -186,12 +181,12 @@ func (h *hub) broadcast(ev wsEvent) {
}
}
// eventType maps a bus subject to its outbound event type, dropping the ws. prefix.
// eventType 把 bus subject 映射为下行事件类型,去掉 ws. 前缀。
func eventType(subject string) string {
return strings.TrimPrefix(subject, wsPrefix)
}
// rawData converts a possibly-empty raw payload into a value safe to publish.
// rawData 把可能为空的原始载荷转换为可安全发布的值。
func rawData(d json.RawMessage) any {
if len(d) == 0 {
return nil