feat(telemetry): opt-in scaffold, default OFF (spec 20) - #97
Merged
Conversation
Ship-empty, privacy-first telemetry scaffold. No real network endpoint is wired: the only sink used is the no-op sink, so even when enabled nothing leaves the machine. - internal/telemetry: allowlist-only Event model (no free-form map / escape hatch), Sink interface, NoopSink (the only wired sink), a pluggable-but- unused HTTPSink (disabled, empty default endpoint, clear TODO), a Recorder consent gate that short-circuits before touching the sink when disabled, and CategorizeError mapping wrapped-error signatures to a closed enum (never the raw err text). - store: telemetry consent persisted in $DEVSTACK_HOME config.yaml (default false); enable mints a UUIDv4 install id + consentAt, disable clears them. - cli: `telemetry status|enable|disable|show`, each with --json; removed the telemetry stub line from stubs.go and wired the real command. Tests: disabled Recorder never calls the sink (mock); enable->disable round-trips in the global config; Event is allowlist-only and redacts raw error strings (no path/IP/secret leak); duration bucketing; error categorize; --json status; `telemetry show` prints only allowlisted keys and does no I/O. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Implements the opt-in, privacy-first usage-telemetry scaffold from spec 20. It is default OFF and, critically, ship-empty: no real network endpoint is wired. The only sink actually used is the no-op sink, so even when a user opts in, nothing leaves the machine.
Changes
internal/telemetry(new):Event— the exhaustive allowlist of coarse, non-identifying fields (command path, flag names, ok/error outcome, error-category enum, bucketed duration, os/arch, is_wsl2, tool version, random install id). Nomap[string]stringescape hatch, so PII is structurally impossible to attach (asserted by a reflection test).Sinkinterface +NoopSink(the only wired sink;DefaultSink()returns it,DefaultEndpointis empty).HTTPSink— a pluggable transport that is unused and disabled by default (empty endpoint = no-op), with a clearly documented TODO to wire a pure-Go OTLP/HTTP exporter once a project-operated collector + privacy policy exist.Recorder— the consent gate; when disabled it short-circuits before ever touching the sink (different branch from "sampled out"), and swallows sink errors (best-effort).CategorizeError— maps wrapped-error signatures to a closed enum; the rawerr.Error()(which can embed paths/IPs/usernames) is never returned or transmitted.internal/store: telemetry consent persisted in the global$DEVSTACK_HOME/config.yaml(never committed, never in the ledger). Defaultfalse;enablemints a UUIDv4 install id +consentAt;disableclears them.internal/cli: realtelemetry status | enable | disable | showgroup, each with a--jsonnon-TTY equivalent.showprints, byte-for-byte, the exact allowlisted event that would be sent and performs zero network I/O. Removed only the telemetry line fromstubs.go.Why
Lets a small OSS team eventually learn coarse usage/failure patterns without ever betraying trust — but ships the trust primitives (consent, allowlist, redaction, radical-transparency
show) first and defers any real endpoint.Ship-empty — no endpoint wired
There is no real network endpoint in this change.
DefaultEndpointis empty andDefaultSink()is the no-op sink; theHTTPSinkexists only as a disabled, documented placeholder. Enabling telemetry currently sends nothing.How tested
CGO_ENABLED=0 go build ./...,CGO_ENABLED=1 go test ./internal/...,gofmt -l(clean),go vet ./...— all green. New tests cover: disabled Recorder never calls a mock sink; enable→disable round-trips in the global config (and doesn't clobber shared services);Eventis allowlist-only and redacts a raw error string (no path/IP/secret leak in the marshaled payload); duration bucketing; error categorization;--jsonstatus; andtelemetry showemits only allowlisted keys.🤖 Generated with Claude Code