Skip to content

feat(usage): opt-in anonymous daily usage ping and self-hosted collector - #5083

Merged
huangruiteng merged 2 commits into
mainfrom
codex/opt-in-usage-ping
Sep 26, 2026
Merged

huangruiteng merged 2 commits into
mainfrom
codex/opt-in-usage-ping

Conversation

@huangruiteng

@huangruiteng huangruiteng commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

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.

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

huangruiteng and others added 2 commits September 26, 2026 21:42
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>

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@huangruiteng
huangruiteng merged commit a5546af into main Sep 26, 2026
25 of 30 checks passed
@huangruiteng
huangruiteng deleted the codex/opt-in-usage-ping branch September 26, 2026 14:09
@huangruiteng

Copy link
Copy Markdown
Collaborator Author

Process correction for the merge at reviewed head 90a4dd200502eadf359400a6ac9ee2528d27054d:

The exact-head review and native validation were published before merge. A readiness read returned ready=true at14:07UTC. The final immediate refresh returned transient merge_state=UNKNOWN / merge_state_unverified. My shell sequence incorrectly continued after a failed assertion and invoked the authorized, head-matched merge. This was a sequencing error, not a valid override of the fail-closed readiness rule.

The merge commit a5546afd113356404c0430423c23f51c96622fc6 contains the unchanged reviewed head; no unreviewed edit was merged. The remaining merge in this task uses a separate checked runner: nonzero readiness exit, non-true ready or head mismatch terminates the process before any merge command. The prior green read does not erase the final sequencing error, which is preserved here for audit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant