feat: v1.0 — télémétrie anonyme opt-in : compteurs agrégés uniquement, settings.telemetry (désactivé par défaut), batch périodique avec backoff, fire-and-forget, zéro PII (closes #76)

- src/telemetry.rs : compteurs par kind + succès/échec des runs, fichier telemetry.json à côté du state, envoi au seuil (10 events ou 7 jours), backoff 3 échecs consécutifs, reset après succès
- app.emit → maybe_record (no-op si désactivé, rien n'est même écrit localement) ; real_main → maybe_flush
- config.yaml : bloc telemetry documenté (enabled + endpoint optionnel)
- 5 tests : disabled (rien), compteurs agrégés (zéro identifiant dans le JSON), local-only sans endpoint, envoi+reset (serveur tiny_http sur thread dédié), backoff
This commit is contained in:
2026-08-19 21:28:26 -04:00
parent 6d51ce9504
commit 7cab1a0b5c
6 changed files with 379 additions and 2 deletions
+19
View File
@@ -136,6 +136,10 @@ pub struct Settings {
/// Push automatically when the REPL exits (issue #66, opt-in).
#[serde(default)]
pub sync_on_exit: Option<bool>,
/// Anonymous opt-in telemetry (issue #76): aggregated counters only.
/// Nothing is collected or sent while `enabled` is false (the default).
#[serde(default)]
pub telemetry: Option<TelemetrySettings>,
/// Plugin scripts (issue #75): default timeout and enable list.
#[serde(default)]
pub plugins: Option<PluginSettings>,
@@ -151,6 +155,18 @@ pub struct Settings {
pub providers: Option<BTreeMap<String, Option<ProviderDef>>>,
}
/// Anonymous opt-in telemetry (issue #76): aggregated counters only — never
/// paths, commands, agent names or identifiers. Disabled by default.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct TelemetrySettings {
/// Explicit opt-in: nothing is collected or sent while false.
pub enabled: bool,
/// Batch endpoint (e.g. https://am-telemetry.example/v1/ping). When
/// unset, counters stay local even when enabled.
pub endpoint: Option<String>,
}
/// One entry of the LLM provider registry (issue #88).
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
@@ -853,6 +869,9 @@ pub fn merge(base: &mut Config, overlay: Config) {
if o.sync_on_exit.is_some() {
s.sync_on_exit = o.sync_on_exit;
}
if o.telemetry.is_some() {
s.telemetry = o.telemetry;
}
if o.plugins.is_some() {
s.plugins = o.plugins;
}