Operationalize the owner report around the honest metric: observed active
installs = distinct rotating anonymous install ids with a session_start
ingested on a UTC day. The canonical report:active-installs command prints
the daily series, a complete-days 7-vs-7 trend, event freshness, and an
always-printed coverage-caveat block; report:dau remains as a pure re-export
compat alias. Exclusion guarantees are now pinned by tests (index1 only
inside count(DISTINCT), no content/identity/network columns anywhere in the
report path), and the output is forbidden from claiming DAU/unique users.
Known floor semantics recorded in docs: day attribution is ingest-day (events
carry no per-event timestamps), distinct counts cannot be sampling-corrected,
and id rotation can double-count across a trend window — all framed as the
lower-bound caveats they are.
Verified: telemetry-ingest npm test (109/109), npm run typecheck, CLI smoke
against fixtures.
Implemented with Claude Code agent assistance.
Move first-run usage disclosure into the native TUI, keep telemetry unarmed until the decision, and order all telemetry writes and delivery against persistent opt-out with fail-closed setup-state handling.
Replace the narrow wide-terminal rail with a responsive full-screen ocean canvas, preserve readable prose measure, and remove per-call padding inside grouped tool activity.
Verified with formatting, clippy, locale/version/budget gates, the 10,120-test TUI library suite, telemetry/config suites, focused UI/PTy coverage, and independent release/UI review. The sandbox-only loopback acceptance rerun remains explicitly environmental.
Agent assistance: Claude and Codex were used for implementation analysis and verification.
Remove the impossible startup-recovery path: arming deliberately truncates every pre-consent buffer, so the startup predicate could only race with events queued by the current process and send them before a mid-session opt-out.
Make the single shutdown flush structural by removing the non-final actor message and public flush API. Strengthen the process-level contract to prove an armed session sends nothing before shutdown, while preserving the shutdown consent re-check and bounded deadline.
Verified with the telemetry unit suite, five focused process-level race runs, the full 263-test integration target, strict workspace all-target/all-feature Clippy, source/runtime/dead-code budgets, formatting, and diff checks.
Every "ships with no endpoint configured" claim in this repo was written when
it was true. It stopped being true when the ingest Worker was deployed and the
client default was wired, so each of those sentences is now a false statement
about a live service. Rewrite them from what the service actually does.
Named plainly, everywhere: batches from an enabled session go to
https://telemetry.codewhale.net/v1/telemetry, a Cloudflare Worker whose
complete source is in this repo under telemetry-ingest/. Storage is Workers
Analytics Engine, whose row is exactly `_sample_interval`, `blob1`-`blob20`,
`dataset`, `double1`-`double20`, `index1`, `timestamp` — there is no IP,
country, or geo column, so storing one is structurally impossible rather than
switched off. The handler reads two headers, never touches the request's geo
properties, logs nothing, and rate-limits on `install_id` rather than an
address. Retention is Cloudflare's fixed three months. `docs/TELEMETRY.md`'s
shipping-gate section becomes a description instead of a promise, and the
retention line it left blank is filled in.
What did NOT change, and is stated at least as loudly as before: telemetry is
opt-in and off by default; nothing is collected, and the telemetry directory is
not created, until the first-run notice is answered with Enable; the endpoint
default decides only where an already-enabled session sends, never whether one
collects; `CODEWHALE_TELEMETRY=0`, `telemetry = false`, and a persisted off
remain hard floors; and the red-line list is untouched. The local dry-run sink
is documented as what it now is — an explicit `telemetry_endpoint = ""` — so a
user can still read their own payloads instead of taking this on trust.
Also stated rather than left implied: what turning telemetry off does and does
not reach. It erases the install id, buffer, and dry-run records locally and
stops everything further; rows already accepted are keyed only by a rotating
random id and age out with the retention window. There is no deletion API and
this does not claim one.
Both roadmap entries are updated in English and Chinese — the opt-in item and
the "Always-on or silent product telemetry" ruled-out item, which now says what
remains ruled out now that an endpoint exists. The public-surface trust string
and the contract-test assertion on it move together; the assertion gains the
consent half and the endpoint name, because a trust claim that says "an
endpoint" without saying which one is not a trust claim.
Gates: cargo test -p codewhale-config, cargo test -p codewhale-telemetry,
telemetry-ingest npm test (87, including the doc weld that parses
docs/TELEMETRY.md), and web check:facts / check:locales / check:docs / lint all
pass. web npm test has one pre-existing unrelated failure in the tool-surface
assertion against docs/TOOL_SURFACE.md, which this change does not touch and
which fails identically at HEAD.
docs/TELEMETRY.md called the opt-out's tombstone "permanent" and said the
buffer was "permanently undrainable". An adversary disproved both in three
commands: opt out, then launch once more, and `buffer::arm` — which every
armed run calls — removed the tombstone and minted a fresh install id. Worse,
the recipe the docs offered as the one-run off switch,
`CODEWHALE_TELEMETRY=0 codewhale`, took the destructive branch, so a consenting
user lost their identity and their own dry-run records every time an agent
harness set that variable for a single command.
The resolver fix underneath this (8d1074f7a) makes the config file the only
register that revokes consent, so both halves follow: a run-scoped kill switch
is `ForcedOff` and touches nothing, and a persisted `telemetry = false` is
re-read on every subsequent launch, which re-asserts the tombstone rather than
relying on it never being cleared. The tombstone is now durable in the way a
file on disk can actually be durable — for exactly as long as the statement
that produced it stands — and the docs say that instead of "permanent".
The "Turning it off" section is rewritten to name the two switches separately:
the config file opts out and erases, the environment variable and the flag stop
the run and erase nothing. That difference is now load-bearing, so it is stated
first rather than left for a reader to infer from one parenthetical.
`CODEWHALE_RELEASE_BUILD_SHA` is emitted only when `DEEPSEEK_BUILD_SHA` or
`GITHUB_SHA` was in the build environment. For every locally built binary it
is simply absent, `option_env!` yields `None`, and a telemetry payload carries
`git_sha: null`.
There is deliberately no fallback to the local checkout, and the "was this a
published release" gate proposed earlier cannot be built as described.
`codewhale_release::latest_release_tag_{async,blocking}` are network calls to
`api.github.com` that return *tag names*, not shas, so the only available
comparison is version-vs-version — and a maintainer running the release build
this repo's own guidance prescribes, on a private tree at the shipping
version, would compare equal and ship their private HEAD. Meanwhile
`build_commit` already falls back to `git rev-parse`, which is why
`CODEWHALE_BUILD_COMMIT` is the builder's own HEAD on every local build and is
named here as a forbidden source. Build-time provenance is deterministic,
network-free, and verifiable from the repository.
The rule lives in `release_build_sha`, which takes the environment as a
closure so it is tested against injected values rather than by mutating the
process: absent, GitHub-only, Codewhale-wins-over-GitHub, a value that is not
a full 40-hex sha, and `CODEWHALE_BUILD_COMMIT` offered as a decoy.
The build script belongs to `crates/telemetry` rather than to a consumer
because `option_env!` reads the environment of the crate being compiled — a
`rustc-env` emitted by `crates/tui`'s build script is not visible to
`crates/telemetry`. Its rerun conditions are the two SHA variables and
nothing about `.git/HEAD`: watching HEAD would rebuild on every local commit
for a value that is `None` on every local build by design.
`docs/TELEMETRY.md` is the schema, field for field, including the section that
states what a receiving endpoint must do before one may be configured at all:
IP-stripped at ingest, a stated retention window, no third-party processor,
and the honest cost of 90-day install-id rotation — no count derived from
`install_id` is a user count. That section is a shipping gate on any
non-loopback endpoint, not a follow-up. Until it is true and filled in, the
shipped default of no endpoint is what makes the rest of the document honest.
The doc is welded to the code, not merely adjacent to it.
`event_field_names_match_documented_schema` parses the fenced `jsonc` blocks
and the field tables out of the file and asserts set equality against the
structs the serializer actually uses. It also asserts the block count and the
per-table row count, so a parser that stops matching fails loudly rather than
passing on an empty parse — a doc-match test that can silently find nothing is
worse than no test, because it reads as coverage.
Two drifts surfaced immediately and are fixed here rather than papered over:
the envelope table never documented `events`, and the errors table collapsed
`provider_http_4xx` and `provider_http_5xx` into one row, so the table was not
in fact one row per field.
`crates/telemetry/tests/golden/v1.json` is one fully-populated instance of the
envelope and every event. `golden_payload_v1` compares against it byte for
byte, so any field add, remove, or retype fails the build until the developer
re-blesses under a bumped `SCHEMA_VERSION`
(`CODEWHALE_BLESS_TELEMETRY_GOLDEN=1 cargo test -p codewhale-telemetry`). It
is also the artifact a future receiver author reads to know exactly what v1
was, which is the part a prose schema cannot provide.
The document also states the things §8 asked for that are not true of this
tree, in the places a reader would otherwise assume them: there is no
factory-reset command, so the guarantee offered is the one that is already
true — the install id is regenerated whenever the telemetry directory is
cleared, which opting out does automatically, and in any case every 90 days.
The footprint ceiling is stated as a number.