From b3ac7b05788fab931a168f679fd263df9724ff39 Mon Sep 17 00:00:00 2001 From: Teodor Calin Date: Wed, 17 Jun 2026 13:59:54 +0300 Subject: [PATCH] fix(telemetry): add missing consent gates to appstore telemetry events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PILOT-401, PILOT-406, PILOT-407 emitted telemetry unconditionally — calling DefaultEndpoint even when the user had not configured a URL. All three blocks now check consent.GetConsent(home, "telemetry") before sending, consistent with review.go. install.sh also showed the wrong config.json structure (root-level keys instead of the required nested {"consent": {...}} object), and claimed "No data is sent unless explicitly enabled" which contradicts the opt-out model. Both corrected. Also fix the go-fmt pre-commit hook to use `git add -u` instead of `git add -A` so untracked files are never inadvertently staged. Co-Authored-By: Claude Sonnet 4.6 --- .pre-commit-config.yaml | 2 +- cmd/pilotctl/appstore.go | 55 ++++++++++++++++-------------- cmd/pilotctl/appstore_catalogue.go | 11 +++--- cmd/pilotctl/appstore_view.go | 43 ++++++++++++----------- install.sh | 15 ++++---- 5 files changed, 68 insertions(+), 58 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6e954618..5b470e5c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: hooks: - id: go-fmt name: go fmt - entry: bash -c 'gofmt -w -s . && git add -A' + entry: bash -c 'gofmt -w -s . && git add -u' language: system files: \.go$ pass_filenames: false diff --git a/cmd/pilotctl/appstore.go b/cmd/pilotctl/appstore.go index f40f8ded..d855a5b6 100644 --- a/cmd/pilotctl/appstore.go +++ b/cmd/pilotctl/appstore.go @@ -37,6 +37,7 @@ import ( "github.com/TeoSlayer/pilotprotocol/pkg/telemetry" "github.com/pilot-protocol/app-store/pkg/ipc" "github.com/pilot-protocol/app-store/pkg/manifest" + "github.com/pilot-protocol/common/consent" "github.com/pilot-protocol/common/crypto" ) @@ -1212,33 +1213,35 @@ func cmdAppStoreInstall(args []string) { Reason: reason, }) - // Emit a telemetry event for the successful install (consent-gated — - // no-op when PILOT_TELEMETRY_URL is empty or identity.json is absent). - // Best-effort: a send failure is logged but not fatal — the install - // itself already succeeded on disk. + // Emit a telemetry event for the successful install. + // Consent-gated (telemetry flag, default on). Best-effort: a send + // failure is logged but not fatal — the install already succeeded on disk. { - url := os.Getenv("PILOT_TELEMETRY_URL") - if url == "" { - url = telemetry.DefaultEndpoint - } - sourceStr := "catalogue" - if source == installSourceLocal { - sourceStr = "local" - } - payload, _ := json.Marshal(map[string]string{ - "app_id": m.ID, - "version": m.AppVersion, - "source": sourceStr, - }) - identityPath := configDir() + "/identity.json" - client := telemetry.NewClientFromIdentity(url, identityPath, 0) - err := client.Send(telemetry.Event{ - Kind: "app_installed", - TS: time.Now().UTC().Format(time.RFC3339), - Payload: payload, - }) - if err != nil { - slog.Warn("telemetry send failed, install still successful", "app", m.ID, "err", err) + home, _ := os.UserHomeDir() + if consent.GetConsent(home, "telemetry") { + url := os.Getenv("PILOT_TELEMETRY_URL") + if url == "" { + url = telemetry.DefaultEndpoint + } + sourceStr := "catalogue" + if source == installSourceLocal { + sourceStr = "local" + } + payload, _ := json.Marshal(map[string]string{ + "app_id": m.ID, + "version": m.AppVersion, + "source": sourceStr, + }) + identityPath := configDir() + "/identity.json" + client := telemetry.NewClientFromIdentity(url, identityPath, 0) + err := client.Send(telemetry.Event{ + Kind: "app_installed", + TS: time.Now().UTC().Format(time.RFC3339), + Payload: payload, + }) + if err != nil { + slog.Warn("telemetry send failed, install still successful", "app", m.ID, "err", err) + } } } diff --git a/cmd/pilotctl/appstore_catalogue.go b/cmd/pilotctl/appstore_catalogue.go index dde7c4c2..7d2be8f3 100644 --- a/cmd/pilotctl/appstore_catalogue.go +++ b/cmd/pilotctl/appstore_catalogue.go @@ -53,6 +53,7 @@ import ( "github.com/TeoSlayer/pilotprotocol/internal/catalogtrust" "github.com/TeoSlayer/pilotprotocol/pkg/telemetry" + "github.com/pilot-protocol/common/consent" ) // defaultCatalogueURL points at the canonical catalogue.json on main. @@ -235,11 +236,11 @@ func cmdAppStoreSignCatalogue(args []string) { } func cmdAppStoreCatalogue(_ []string) { - // Emit a telemetry event for the catalogue page view (consent-gated — - // no-op when PILOT_TELEMETRY_URL is empty or identity.json is absent). - // Best-effort, non-blocking: a send failure is logged but doesn't - // prevent the catalogue from rendering. - { + // Emit a telemetry event for the catalogue page view. + // Consent-gated (telemetry flag, default on). Best-effort: a send + // failure is logged but doesn't prevent the catalogue from rendering. + home, _ := os.UserHomeDir() + if consent.GetConsent(home, "telemetry") { url := os.Getenv("PILOT_TELEMETRY_URL") if url == "" { url = telemetry.DefaultEndpoint diff --git a/cmd/pilotctl/appstore_view.go b/cmd/pilotctl/appstore_view.go index 347b6582..618de385 100644 --- a/cmd/pilotctl/appstore_view.go +++ b/cmd/pilotctl/appstore_view.go @@ -29,6 +29,7 @@ import ( "github.com/pilot-protocol/app-store/pkg/manifest" "github.com/TeoSlayer/pilotprotocol/pkg/telemetry" + "github.com/pilot-protocol/common/consent" ) // installedAppFacts is the verified, local-only band of `view` — derived @@ -179,27 +180,29 @@ func cmdAppStoreView(args []string) { "app %q not found in catalogue or install root", appID) } - // Emit a telemetry event for the detail view (consent-gated — - // no-op when PILOT_TELEMETRY_URL is empty or identity.json is absent). - // Best-effort: a send failure is logged but not fatal — the view - // itself already resolved and rendered below. + // Emit a telemetry event for the detail view. + // Consent-gated (telemetry flag, default on). Best-effort: a send + // failure is logged but not fatal — the view already resolved and rendered below. { - url := os.Getenv("PILOT_TELEMETRY_URL") - if url == "" { - url = telemetry.DefaultEndpoint - } - payload, _ := json.Marshal(map[string]string{ - "app_id": appID, - }) - identityPath := configDir() + "/identity.json" - client := telemetry.NewClientFromIdentity(url, identityPath, 0) - err := client.Send(telemetry.Event{ - Kind: "appstore_view", - TS: time.Now().UTC().Format(time.RFC3339), - Payload: payload, - }) - if err != nil { - slog.Warn("telemetry send failed, view still shown", "app", appID, "err", err) + home, _ := os.UserHomeDir() + if consent.GetConsent(home, "telemetry") { + url := os.Getenv("PILOT_TELEMETRY_URL") + if url == "" { + url = telemetry.DefaultEndpoint + } + payload, _ := json.Marshal(map[string]string{ + "app_id": appID, + }) + identityPath := configDir() + "/identity.json" + client := telemetry.NewClientFromIdentity(url, identityPath, 0) + err := client.Send(telemetry.Event{ + Kind: "appstore_view", + TS: time.Now().UTC().Format(time.RFC3339), + Payload: payload, + }) + if err != nil { + slog.Warn("telemetry send failed, view still shown", "app", appID, "err", err) + } } } diff --git a/install.sh b/install.sh index 00bd5aa0..64bd57d4 100755 --- a/install.sh +++ b/install.sh @@ -730,20 +730,23 @@ echo " reviews — occasional prompt to review installations" echo " skillinject — auto-inject the Pilot Protocol skill into" echo " agent toolchains" echo "" -echo " Disable any feature by setting \"false\" in config.json:" +echo " Disable any feature by adding \"false\" entries under the \"consent\"" +echo " key in config.json:" echo " ${PILOT_DIR}/config.json" echo "" -echo " \"telemetry\": false # suppress interest signals" -echo " \"broadcasts\": false # block agent-directed broadcasts" -echo " \"reviews\": false # suppress review prompts" +echo " {\"consent\": {" +echo " \"telemetry\": false, // suppress interest signals" +echo " \"broadcasts\": false, // block agent-directed broadcasts" +echo " \"reviews\": false // suppress review prompts" +echo " }}" echo "" echo " Skillinject has its own CLI commands:" echo " pilotctl skills disable # remove injected skills, stop future ticks" echo " pilotctl skills enable # re-install and re-enable" echo "" echo " Config changes take effect on daemon restart (or immediately for" -echo " skillinject). No data is sent to Pilot Protocol LLC or any third" -echo " party unless you explicitly enable it." +echo " skillinject). Telemetry features are ON by default (opt-out model)." +echo " Set the consent flags above to false to disable them." echo "" echo "============================================" echo ""