Files
deepseek-harness/.agents/notes/implemented/feature/2026-07-31-telemetry-anonymous-user-id.zh.md
T

45 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Agent Note: telemetry 匿名用户 id($DSH_HOME/.userid)与 OTel Resource user.id
Status: implemented
[English](2026-07-31-telemetry-anonymous-user-id.md) | 中文
## Problem
session telemetry 已默认挂载([默认挂载 Note](2026-07-31-web-telemetry-default-mount.md)),但 OTel Resource 只有 `service.name`/`service.version`,没有任何用户级标识——接收端无法按用户聚合、无法数活跃用户。此前唯一相关口径是一条未实现的「hostname/本机 IP 哈希派生 user.id」裁定;dsh-sdk 工具链另有自用的匿名 id(`$DSH_HOME/telemetry.json`),但那是 launcher 回流的私有事实,与 OTel 回流无关。需要给 OTel 回流一个语义干净的匿名用户身份。
## Decision
`session-telemetry-otel` 包内模块 `src/user-id.ts` 是 OTel 回流用户身份的属主:`getOrCreateAnonymousUserId()` 返回 `$DSH_HOME/.userid`(`resolveDshHome` 解析,`$DSH_HOME` > `~/.dsh`)中的裸 UUID 行,首用生成随机 UUID v4 并落盘;backend 构造时把它作为 Resource 的 `user.id`(OTel semconv 标准用户属性)随每批导出携带一次。该身份只属于 OTel 回流;dsh-sdk launcher telemetry 保留自己的匿名 id 存储(`telemetry.json`),两者不共享(初版曾做公用 util 包统一两条回流,用户复议后收回:在有第二个真实消费者之前不抽公共包,回流关联需求出现时再议)。
| 裁定 | 取值 | 理由 |
|---|---|---|
| id 来源 | 随机 UUID v4,绝不从 hostname/网络地址/git remote 派生 | 派生 id 可反查,「匿名」名不副实 |
| 存储形态 | `.userid` 裸 UUID 行 + 换行,无 JSON 包装 | 身份是独立事实,不挂在某条 telemetry 链路的文件命名/格式下 |
| 读写形态 | 同步 IO + 进程内按解析后文件路径 memo | `TelemetryOtel` 构造函数是同步的(async 迫使插件装载改形);一进程一次盘 IO,运行中删文件不影响本进程 |
| 并发首启 | `wx` 独占写裁决,落败方重读胜者 id | 覆盖常见并发(重读撞进胜者建档-写入微秒窗仍可能各持一 id 一次运行,下次启动收敛到落盘值——telemetry 级后果,接受) |
| 丢失语义 | 文件被删 → 下次启动换新 id,接受丢失 | 匿名身份无恢复价值;可恢复性要求派生材料,与匿名冲突 |
| 写失败 | best-effort 返回内存 id | telemetry 永不因 home 只读被阻塞 |
| 上报位置 | Resource 属性,非逐条 attributes | 每批一次即够接收端按 Resource 维度聚合;逐条注入要动 seam 契约且涨 wire 体积 |
| semconv 依赖 | 不引 `@opentelemetry/semantic-conventions` 包 | 一个字符串常量不值一个依赖 |
| 落点 | `session-telemetry-otel` 包内模块,非公共 util 包 | 仓规「有第二个真实消费者才拆包」;sdk launcher 回流保留自有存储,无现实关联需求 |
| 单独开关 | 无 | 身份跟随 telemetry 整体开关(`DSH_TELEMETRY_DISABLED`);关 telemetry 即整体不报 |
## Alternatives considered
| 被拒 | 一句话理由 |
|---|---|
| hostname/IP 哈希派生 id(此前口径) | 可反查即非匿名;随机 UUID 语义干净,用户裁决取代 |
| user.id 放每条 record 的 attributes(Claude Code 形态) | 要动 session-telemetry seam 契约或逐条注入,wire 体积涨;Resource 每批一次已满足聚合 |
| 公用 util 包统一两条回流(初版实现) | 唯一现实消费者是 OTel backend;sdk launcher 换用它只是为统一而统一——用户复议收回,回流关联需求出现时再抽包 |
| 复用 telemetry.json 不新建文件 | 文件名/JSON 格式把身份挂在 launcher 链路命名下;OTel 回流身份是独立事实 |
| AppCLIEntry 读好 id 经 config patch 注入 | 每个 surface 入口都要接线;config 里传运行时事实与部署配置混淆 |
| 挂进 `@deepseek-ai/dsh-paths` | paths 是纯路径计算零 IO;带持久化的身份能力会污染包边界 |
## Consequences
- 一个 `$DSH_HOME` 在 OTel 回流中是一个稳定用户;不同 home 在构造上就是不同用户,无跨 home 关联机制。
- OTel 回流与 launcher 回流各有各的 id(`.userid` 与 `telemetry.json`),无法互相关联——这是「不抽公共包」的直接代价,等真实关联需求出现再统一。
- 删除 `.userid` 即重置身份(下次启动生效);home 不可写时每进程各自持有一个内存 id 直至恢复可写。
- [默认挂载 Note](2026-07-31-web-telemetry-default-mount.md) 的身份 follow-up 中「匿名用户 id」项由本决定关闭;hostname/surface 维度与脱敏规则、usage-metrics track 仍是待办。