Summary
turns 表存在三个数据质量问题,导致运营数据分析(用户成本归因、模型效率对比、crash 追踪)不可用。
数据库审计结果:816 turns(492 assistant),全部受影响。
数据证据
-- 所有 session 的 owner_id 为空,user_id 有值
SELECT owner_id, COUNT(*) FROM sessions GROUP BY owner_id;
-- result: '' → 308 sessions
-- turns.user_id 全部为空
SELECT user_id, COUNT(*) FROM turns WHERE role='assistant' GROUP BY user_id;
-- result: '' → 492 turns
-- Slack session 的 model 全部为空
SELECT s.platform, t.model, COUNT(*)
FROM turns t JOIN sessions s ON t.session_id = s.id
WHERE t.role='assistant' GROUP BY s.platform, t.model;
-- result: feishu/glm-5.1 → 220, feishu/'' → 1, slack/'' → 32
根因链
Bug 1: user_id 始终为空(全部 492 assistant turns)
sessions.owner_id 列在 session 创建时从未被赋值(所有 308 条记录为空字符串)
- SQL 查询层
COALESCE(owner_id, user_id) 提供了读取时 fallback,但仅对 DB 查询生效
bridge_forward.go:forwardEvents 初始化 forwardContext 时调用 b.sm.Get():
- 活跃 session 从内存 map 读取(
manager.go),内存中 SessionInfo.OwnerID 为创建时的原始值(空字符串)
fc.sessOwner = si.OwnerID → 始终为空
captureAssistantTurn 使用 fc.sessOwner 设置 TurnWriteRequest.UserID → 写入空字符串
影响:无法按用户维度做成本归因和使用分析。
Bug 2: Slack session model 始终为空(32/32 turns)
session_stats.go:mergePerTurnStats 从 data.Stats["model_usage"](CC 格式)或 data.Stats["tokens"](OCS 格式)提取 model
- Slack Worker 的 done 事件不包含这两个 stats 字段
acc.ModelName 保持为零值(空字符串)
captureAssistantTurn 写入 Model: acc.ModelName → 空字符串
影响:无法按模型分析 Slack 渠道的成本效率。
Bug 3: captureSyntheticEvent 缺少 Platform/UserID/Model
bridge_forward.go:543-573 — crash/timeout/fresh_start 场景的 synthetic turn:
turn := &eventstore.TurnWriteRequest{
SessionID: sessionID,
Generation: acc.Generation,
TurnNum: acc.TurnCount,
// Platform, UserID, Model — 全部未设置
...
}
影响:crash/timeout turn 无法关联到用户和平台,降低了运维可观测性。
修复方案
Bug 1 修复(优先级 P2)
方案 A(推荐):在 forwardEvents 初始化 forwardContext 时增加 fallback:
// bridge_forward.go forwardEvents()
if si, err := b.sm.Get(context.Background(), sessionID); err == nil {
fc.sessPlatform = si.Platform
fc.sessOwner = si.OwnerID
if fc.sessOwner == "" {
fc.sessOwner = si.UserID // fallback to user_id
}
}
方案 B(根治):确保 session 创建时填充 owner_id。追踪 StartSession → GetOrCreate → upsert SQL,在创建路径中 owner_id = user_id。
Bug 2 修复(优先级 P3)
在 Slack Worker 适配器中确保 done 事件包含 model_usage 或在 mergePerTurnStats 中增加备用来源(如从 OCS converter 已有的 takeStats 路径获取)。
Bug 3 修复(优先级 P3)
captureSyntheticEvent 补充 Platform、UserID、Model 字段:
turn := &eventstore.TurnWriteRequest{
...
Platform: fc.sessPlatform, // 需传入 forwardContext
UserID: fc.sessOwner, // 需传入 forwardContext
Model: acc.ModelName,
...
}
当前 captureSyntheticEvent 不接收 forwardContext,需扩展签名或在调用点补充。
Acceptance Criteria
Summary
turns 表存在三个数据质量问题,导致运营数据分析(用户成本归因、模型效率对比、crash 追踪)不可用。
数据库审计结果:816 turns(492 assistant),全部受影响。
数据证据
根因链
Bug 1: user_id 始终为空(全部 492 assistant turns)
sessions.owner_id列在 session 创建时从未被赋值(所有 308 条记录为空字符串)COALESCE(owner_id, user_id)提供了读取时 fallback,但仅对 DB 查询生效bridge_forward.go:forwardEvents初始化forwardContext时调用b.sm.Get():manager.go),内存中SessionInfo.OwnerID为创建时的原始值(空字符串)fc.sessOwner = si.OwnerID→ 始终为空captureAssistantTurn使用fc.sessOwner设置TurnWriteRequest.UserID→ 写入空字符串影响:无法按用户维度做成本归因和使用分析。
Bug 2: Slack session model 始终为空(32/32 turns)
session_stats.go:mergePerTurnStats从data.Stats["model_usage"](CC 格式)或data.Stats["tokens"](OCS 格式)提取 modelacc.ModelName保持为零值(空字符串)captureAssistantTurn写入Model: acc.ModelName→ 空字符串影响:无法按模型分析 Slack 渠道的成本效率。
Bug 3: captureSyntheticEvent 缺少 Platform/UserID/Model
bridge_forward.go:543-573— crash/timeout/fresh_start 场景的 synthetic turn:影响:crash/timeout turn 无法关联到用户和平台,降低了运维可观测性。
修复方案
Bug 1 修复(优先级 P2)
方案 A(推荐):在
forwardEvents初始化forwardContext时增加 fallback:方案 B(根治):确保 session 创建时填充
owner_id。追踪StartSession→GetOrCreate→ upsert SQL,在创建路径中owner_id = user_id。Bug 2 修复(优先级 P3)
在 Slack Worker 适配器中确保 done 事件包含
model_usage或在mergePerTurnStats中增加备用来源(如从 OCS converter 已有的takeStats路径获取)。Bug 3 修复(优先级 P3)
captureSyntheticEvent补充Platform、UserID、Model字段:当前
captureSyntheticEvent不接收forwardContext,需扩展签名或在调用点补充。Acceptance Criteria
user_id非空(来自 session 的COALESCE(owner_id, user_id))model字段非空platform和user_idmake check全量通过