feat(usage): opt-in anonymous daily usage ping and self-hosted collector - #5083
Conversation
huangruiteng
left a comment
There was a problem hiding this comment.
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
结论:REQUEST_CHANGES。 完整评审 head 4c6198f9d2e21002d4d58c24e6635d5d1ab42bf3,不可变基线 8189625741b33c7ba3b55b6068205909aa9f9ba3。阻塞是实际并发下的授权撤回失效:后台请求完成会覆盖刚执行成功的 disable,恢复 enabled 和旧安装 ID。
动机
用最小、明确 opt-in 的 installation ping 改善活跃安装数量的估计,是一个具体的产品目标。机器 owner 的同意是不可从安装、endpoint、Goal 激活或普通命令推断的意图;显式 enable/disable 和可以检查的精确 payload 是必要入口。PR 还说明 default endpoint 为空,部署 collector 与设定发布 endpoint 是维护者的下一阶段,这个边界有利于独立验证与回滚。但隐私承诺需要覆盖后续重复命令和授权撤回:当前 disable 的成功结果不能保持到一次旧请求完成之后,旧身份和后续发送资格会被复活,因此这次 client 阶段尚未完成验收。
改动思路
client 在单独的 usage_ping.py 管机器级同意、随机 ID、UTC day 和六字段 payload,普通 CLI 在共享 runtime hook 中启动同一解释器的 detached child。网络超时在 child 中,显式 usage-ping 命令只预览或改变授权。collector 的纯 handler 与 Cloudflare Worker adapter 分开,SQL 接收按 installation/day 去重并提供统计与保留期。这个边界没有引入注册 peer、Goal 权限或 account-wide 授权。现有 first-run feedback 是主动的本地反馈,不具备每天计数的同一合同;它不能替代这次 consent。仓库已有跨进程文件锁,适合复用来保护短状态变更;原子 replace 只保护文件完整性,无法保护 owner 最新意图。
具体改动
23 个文件包括 client、独立的 CLI register/handler、cli.py 与 cli_commands export、cli_runtime hook、cold-path output-budget 分类、help/generated manpage 和 installer 的 availability hint。英文/中文 README 与 reference 文档说明默认关闭、机器范围、环境阻断、endpoint、payload、关闭与统计限制;这些发现入口没有自动启用同意。optional collector app 包含 SQL、严格验证及统计/retention handler、Worker 的 fetch/scheduled 绑定、关闭 observability 的配置示例、部署 README 和忽略规则,以及 real SQLite Node 测试和 Python CI runner。collector 尚未实际部署,此评审不把部署说明当成默认服务已经可用,也不替维护者设置 endpoint。
关键代码讲解
usage_ping.py:88的disable写入 disabled,并删除 installation ID。这是用户撤回授权的实际持久化入口,不只是提示文案;后来的旧 sender 不得恢复这个被删除的 ID。usage_ping.py:160的send在网络前读取 enabled snapshot,检查环境、endpoint 和当天发送,再调用真实 POST。问题位于成功后的整个旧 snapshot 保存:它没有确认当前同意与 ID 仍属于这次请求。usage_ping.py:202的maybe_schedule在普通命令前运行。初始 off/disabled 路径不写状态、不发送;enabled 路径在 spawn 前写当天 attempt 并启动 detached child。这里与 explicit controls、sender 共同修改一个文件,必须共享短状态变更 owner,不能把 replace 当成并发 claim。collector.js:67的recordPing通过 SQL batch 保存初见安装和每天一行的 payload,buildStats再进行聚合与小 bucket 合并。它在实际 SQLite 上验证,能防计数重复,无法修复 client 的授权撤回;Worker 只是 provider adapter。
对主干的风险
[P1] 旧后台发送完成会撤销用户刚做出的 disable。 成功后的保存 继续写网络前读取的 enabled snapshot。我使用隔离的合成授权文件和真实回环 HTTP,完整走了实际 CLI:usage-ping enable → 普通 version 命令启动真实 detached sender → HTTP server 接到请求后暂不返回 → 实际 CLI usage-ping disable 返回 disabled → server 返回 204 → 另一次实际 CLI status。最后得到 consent=enabled、sending=true,而且旧 install ID 恢复。独立的“撤回不能被旧请求完成覆盖”断言失败。测试没有操作实际安装的 owner 授权,也没有调用公共 collector。
这不要求取消已经发出的请求;问题是完成回写恢复了未来发送资格和已明确删除的身份。后续 UTC 日的普通命令因此可以继续上报而没有新的同意。相同的 stale writer 也可能覆盖 disable 后新 enable 的身份。
最低修复:在现有机器 consent owner 内复用跨进程锁保护短的 read/modify/write,并用当前 consent generation 或 installation identity 对完成 merge 做比较;若 disable 或新 enable 已发生,丢弃旧完成回写。attempt claim 也应使用同一原子边界。不要在网络请求期间持锁、让普通命令等待 3 秒。增加实际 CLI + detached child + held HTTP response 的 disable 与 disable/re-enable 回归,分别读回 disabled/no-ID 或新 ID,证明旧完成不能替换最新 owner 选择。
本地验证:15 个 focused pytest 用例通过,其中 collector runner 包含 7 个真实 Node SQLite 用例;23 个 CLI output-budget 用例通过;help/manpage smoke 与 generated manpage check 通过。使用同一个 base/head fixture,在 endpoint 已配置的 undecided、disabled 两种场景,实际 version JSON 相同、授权文件字节未变、没有新增运行时文件、HTTP 请求为零。这个正面的初始 off parity 不能替代并发关闭路径的失败。六个 payload 字段、错误/环境阻断、collector 校验/聚合/去重/retention 由 focused tests 覆盖。
premerge 执行 19 项,18 项通过,唯一失败是 install-local-smoke.py:416 缺少 compact skill 的 This command is read-only。同一检查在不可变基线与 head 以同一断言失败,相关 skill 生成逻辑未改;它是独立的维护者资格问题,不归因于新的 installer hint,也不要求这个 PR 修无关 skill。packaged install、update、CLI modularization、semantic vocabulary、output regression 和改动路径 public boundary checks 通过。没有读取或等待远端 CI;Cloudflare 实际部署、native Windows detached 分支以及高并发每日 claim 尚未验证,不宣称全平台或并发 at-most-once 已完成。
语义与 CI 对齐
当前被违反的是已明确声明的 owner consent/identity retirement 合同。installation、help、endpoint readiness 只是 availability;不能给 agent 自动启用授权。machine scope 按文档有意覆盖后续普通命令,但仅在当前同意仍然有效时成立。关闭路径必须是机器执行的拒绝边界,不能改成“用户再运行一次 disable”的指导来掩盖失效。修复后重跑实际授权 race/readback、初始 off 对照、tests/test_usage_ping.py、tests/test_usage_collector.py 与风险选择 premerge。主干 ruleset 要求 Sign-off 和 merge-gate;本次 review 不授予部署或合并权限。
我的整体评价
REQUEST_CHANGES。client 与可独立部署的 collector 是范围明确、可运行的阶段,payload 最小化、显式 CLI 和 provider 分层有价值。主要修复无需新 authority framework,应在同一个 state owner 中收敛同步与结果 merge,并保持前台路径轻量。未来维护检查选择了复用现有跨进程锁和小的状态转换边界;collector SQL 与 Worker 的职责应继续分开。修复 owner 撤回和新身份保护后,部署及 release default 仍由已有维护者下一步承担;当前通过的顺序测试不能证明这个并发授权合同。
English verdict: REQUEST_CHANGES - head 4c6198f. A real CLI-spawned background sender overwrites a successful disable after HTTP completion, restoring enabled consent and the retired installation ID. Fence short state transitions and completion merges against current owner intent. Focused client/SQLite, initial-off parity, output and most local canaries passed; the install smoke failure reproduces identically at the immutable base. No collector deployment or merge is authorized by this review.
Add `loopx usage-ping [status|enable|disable]`. The ping is off unless the machine owner enables it; LoopX never prompts. When enabled, at most one six-field payload (random install id, version, OS family, Python minor, install channel) is posted per UTC day from a detached child process. CI, DO_NOT_TRACK and LOOPX_USAGE_PING=0 block sending. The release default endpoint stays empty, so nothing is sent until the collector in apps/usage-collector (Cloudflare Worker + D1) is deployed and DEFAULT_ENDPOINT is set. The collector stores only payload fields plus the UTC day and publishes monthly active / new installs with small buckets suppressed. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…-in help Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
4c6198f to
90a4dd2
Compare
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
动机
为自愿用户提供最小安装统计,并让机器所有者知道如何开启、检查与关闭。复核整个最终24文件 diff,原来的授权撤回回写问题已修复。
改动思路
沿用机器 consent owner 与现有跨进程锁;短状态变更加锁,HTTP 在锁外。后台任务携带安装 ID,完成时只给仍然匹配的当前身份合并成功日期。客户端日限流和 collector 的接收去重属于不同边界,分别保留各自 owner。
具体改动
重复 enable 保留 UUID 和当日状态;disable 删除身份;旧 child/请求不能覆盖新的授权。设置页 Other 加入中英双语、自愿开启说明和 enable/status/disable 命令。纯 collector 与 Cloudflare/D1 适配层保持分离,不增加第二个前端 consent store。普通 CLI 在未决定、关闭或环境禁用时直接跳过;启用状态的并发 claim 使用 SINGLE_FLIGHT,竞争时跳过,因此不会等待后台 HTTP。公共文档说明已经发出的请求无法撤回,后续命令和新身份仍以当前 owner 意图为准。
对主干的风险
默认仍关闭,默认 endpoint 尚为空。18 个客户端/真实 SQLite collector pytest 用例通过,包含真实 CLI、detached child、持有 HTTP 响应的撤回/重新授权,以及8进程并发 claim。未决定/已关闭的 base/head 普通 CLI 输出、状态和零请求一致。前端构建及 packaged zh/en 桌面/移动端、键盘展开验证通过;89个输出测试及 emitted CLI budget smoke 通过。最终 standard canary 全部19项通过,packaged install/update、公共边界与 exact-scope quality receipt 均通过。install-local 早前120/180秒预算内超时,同一检查独立运行通过,最终按有限安装工作量重跑600秒预算通过;保留原超时观察,不将超时记为通过。未完成 Cloudflare 部署或 Windows 原生进程验证;公网匿名计数的滥用/下界限制已披露。
我的整体评价
没有阻塞发现。与未来改动相关的 bounded refactor 已应用:复用锁、身份围栏、字段合并和现有设置说明,避免新的状态权威。客户端与 collector 阶段可独立审阅、回滚;服务部署是本次用户任务的下一步,当前不宣称默认服务已运行。
English verdict: APPROVE - head 90a4dd2. Consent withdrawal, fresh identity and daily concurrency are protected on real CLI/process/HTTP paths; feature-off behavior and packaged discovery are verified. Collector provisioning remains an explicit next stage.
|
Process correction for the merge at reviewed head The exact-head review and native validation were published before merge. A readiness read returned The merge commit |
Adds explicit machine-owner, default-off daily anonymous installation measurement with status/enable/disable controls and a deployable Cloudflare/D1 collector. The payload contains only a random installation ID, LoopX version, OS family, Python minor version and install channel. Undecided/disabled installations send nothing; environment suppressors override consent. The default service endpoint remains empty pending separately verified deployment.
Short existing cross-process locks protect consent and daily claims; detached sends carry the installation ID and successful responses only update the still-current identity. Disable/re-enable cannot be overwritten by old requests, and repeated enable preserves daily markers. Existing machine settings show a bilingual optional notice, fields/scope, terminal controls and privacy documentation; consent remains owned by the CLI machine utility.
Validation:18 client/collector pytest cases including real CLI/detached child/held HTTP withdrawal and renewal, eight-process claim, and seven real SQLite collector cases;89 CLI-output tests and emitted-output smoke; frontend build and packaged zh/en desktop/mobile keyboard/readback checks. Final standard canary19/19passed, public boundary scans and exact-diff quality qualification passed. Earlier short-budget install timeouts are disclosed in the exact-head review; isolated and final600s-budget check passed. Native Windows and deployed Cloudflare service are not claimed validated. Bounded refactor reused the lock, identity fence and existing settings notice instead of another consent store.