Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

109 lines
3.9 KiB
Go

// Package telemetry implements opt-in, anonymous usage telemetry for Gortex.
//
// The cardinal rule is opt-in: nothing is recorded, buffered, or sent unless
// consent resolves to enabled, and the default at every layer is off. This
// file holds only the consent decision — the single source of truth every
// other telemetry path consults before doing anything.
package telemetry
import (
"os"
"strings"
)
// Source identifies which precedence rung decided consent — surfaced by
// `gortex telemetry status` and useful when a user asks "why is this on/off".
type Source string
const (
// SourceEnv: the GORTEX_TELEMETRY environment override.
SourceEnv Source = "env"
// SourceDoNotTrack: the cross-tool DO_NOT_TRACK standard.
SourceDoNotTrack Source = "do_not_track"
// SourceConfig: the persisted telemetry.enabled config value.
SourceConfig Source = "config"
// SourceDefault: no signal at any rung — the opt-in default (off).
SourceDefault Source = "default"
)
// Consent is the resolved telemetry decision plus the rung that produced it.
type Consent struct {
Enabled bool
Source Source
}
// ConsentConfig is the persisted-config view the resolver needs: the explicit
// telemetry.enabled value, or nil when the config says nothing about it (so the
// resolver can fall through to the default rather than treating "unset" as off).
type ConsentConfig struct {
Enabled *bool
}
// Environment variables consulted by the resolver.
const (
// EnvTelemetry is Gortex's explicit per-process override.
EnvTelemetry = "GORTEX_TELEMETRY"
// EnvDoNotTrack is the cross-tool standard (https://consoledonottrack.com).
EnvDoNotTrack = "DO_NOT_TRACK"
)
// ResolveConsent decides whether anonymous usage telemetry is enabled, applying
// a fixed four-rung precedence (highest wins). Telemetry is opt-in: with no
// signal at any rung it is off.
//
// 1. GORTEX_TELEMETRY — an explicit per-process override. A recognised off
// value (0/false/off/no/disable) forces off; an on value (1/true/on/yes/
// enable) forces on. Highest so a user can always override everything,
// including a global DO_NOT_TRACK, for one invocation.
// 2. DO_NOT_TRACK — the cross-tool privacy standard. Any value other than
// unset / "0" / "false" forces off. It can only ever disable, never enable.
// 3. config telemetry.enabled — the persisted user choice.
// 4. default — off.
//
// getenv defaults to os.Getenv when nil; tests inject a fake lookup.
func ResolveConsent(cfg ConsentConfig, getenv func(string) string) Consent {
if getenv == nil {
getenv = os.Getenv
}
// Rung 1: explicit Gortex env override (can force on or off).
if v, ok := parseConsentBool(getenv(EnvTelemetry)); ok {
return Consent{Enabled: v, Source: SourceEnv}
}
// Rung 2: DO_NOT_TRACK (off-only signal).
if doNotTrackAsserted(getenv(EnvDoNotTrack)) {
return Consent{Enabled: false, Source: SourceDoNotTrack}
}
// Rung 3: persisted config.
if cfg.Enabled != nil {
return Consent{Enabled: *cfg.Enabled, Source: SourceConfig}
}
// Rung 4: opt-in default.
return Consent{Enabled: false, Source: SourceDefault}
}
// parseConsentBool maps an on/off-ish string to a bool. ok is false for "" or
// an unrecognised value, so the caller falls through to the next rung instead
// of treating noise as a decision.
func parseConsentBool(s string) (val, ok bool) {
switch strings.ToLower(strings.TrimSpace(s)) {
case "1", "true", "on", "yes", "enable", "enabled":
return true, true
case "0", "false", "off", "no", "disable", "disabled":
return false, true
default:
return false, false
}
}
// doNotTrackAsserted reports whether DO_NOT_TRACK requests no tracking. Per the
// standard a set value that is not "0"/"false" means "do not track"; unset,
// "0", and "false" do not assert it.
func doNotTrackAsserted(s string) bool {
s = strings.ToLower(strings.TrimSpace(s))
return s != "" && s != "0" && s != "false"
}