From db80b75814b608d216bc2c5a84456755286794df Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:04:52 -0400 Subject: [PATCH] Docs: privacy.md lists every usage event; release builds get the key (#238) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo --- .github/workflows/release.yml | 8 ++++ README.md | 7 ++- docs/crash-reports.md | 6 +-- docs/privacy.md | 84 +++++++++++++++++++++++++++++++++++ docs/releasing.md | 7 +++ 5 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 docs/privacy.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0b0fece..ac0df31 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -97,6 +97,8 @@ jobs: - name: Build, sign, notarize and package (the dmg is mounted and checked) id: package env: + # The analytics project key (docs/privacy.md), built in; never printed. + FIELDCRAFT_POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} @@ -139,6 +141,9 @@ jobs: cargo install --locked --version "$CARGO_DEB_VERSION" cargo-deb cargo install --locked --version "$CARGO_GENERATE_RPM_VERSION" cargo-generate-rpm - name: Build and package (tar.gz, AppImage, deb, rpm) + env: + # The analytics project key (docs/privacy.md), built in; never printed. + FIELDCRAFT_POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} run: packaging/linux/package.sh - name: Check the packages run: | @@ -211,6 +216,9 @@ jobs: save-if: ${{ github.ref == 'refs/heads/main' }} - name: Build and package (zip) shell: pwsh + env: + # The analytics project key (docs/privacy.md), built in; never printed. + FIELDCRAFT_POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} run: ./packaging/windows/package.ps1 - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: diff --git a/README.md b/README.md index 91ee67a..d70797c 100644 --- a/README.md +++ b/README.md @@ -137,8 +137,11 @@ Rust 1.95 or later, with the `wasm32-unknown-unknown` target for `cargo xtask ci ## Privacy -FieldCraft sends nothing anywhere on its own: no analytics, no telemetry, no automatic crash -upload. Crash reports and the log stay on your computer; Help › Report a Bug opens a GitHub issue +FieldCraft sends nothing until you agree. The first start asks once whether to share anonymous +usage (which tools you use, nothing from your models); Help › Settings… changes it, and +`FIELDCRAFT_TELEMETRY=0` turns it off. File names, paths, geometry, model contents, parameter +values and messages are never sent: [docs/privacy.md](docs/privacy.md) lists every event and +property. Crash reports and the log stay on your computer; Help › Report a Bug opens a GitHub issue you review and submit yourself, with your session or model attached only if you tick them ([docs/crash-reports.md](docs/crash-reports.md)). diff --git a/docs/crash-reports.md b/docs/crash-reports.md index f1e5246..d1034fa 100644 --- a/docs/crash-reports.md +++ b/docs/crash-reports.md @@ -2,9 +2,9 @@ ## Privacy -FieldCraft sends nothing anywhere on its own: no analytics, no background telemetry, no -automatic crash upload. Crash reports and the log stay on this computer. The only way anything -leaves it is **Help › Report a Bug** (or **Report It…** after a crash), which opens a GitHub issue +No crash report, log line or model is uploaded automatically. Crash reports and the log stay on +this computer (anonymous usage, if you agreed to share it, never carries them: see +[privacy.md](privacy.md)). The only way they leave it is **Help › Report a Bug** (or **Report It…** after a crash), which opens a GitHub issue form in your browser; you read it there and submit it, or not. What a bug report carries: diff --git a/docs/privacy.md b/docs/privacy.md new file mode 100644 index 0000000..cdfa44b --- /dev/null +++ b/docs/privacy.md @@ -0,0 +1,84 @@ +# Privacy and anonymous usage + +FieldCraft can share **anonymous usage** with its developers: which tools are used, where people +get stuck and how far they get from a design to results. It is **off until you agree**. The first +time the app starts it asks once: + +> Help improve FieldCraft by sharing anonymous usage: which tools you use, nothing from your models. + +**Share Anonymous Usage** turns it on, **Don't Share** (or closing the question) leaves it off. +Change it any time in **Help › Settings… › Privacy**, which also has **Include scripts and AI +agents (MCP)**: commands run by scripts, the control channel and AI agents are counted apart from +yours (`from`), and this leaves them out. + +Crash reports and the log never leave your computer on their own; Help › Report a Bug sends only +what you submit yourself ([crash-reports.md](crash-reports.md)). + +## Never sent + +File names or paths, geometry, meshes, model contents, parameter values (sizes, materials, +loads…), names you give things, error messages, or anything else you typed. The events below are +built from FieldCraft's own lists (its command ids, sample names and requirement names) and +durations, so they cannot carry any of it; a test runs a scripted session and fails if a payload +holds anything else. + +## When nothing is sent + +- until you agree, and after you turn it off (what was waiting is dropped); +- in builds without an analytics key: everything built from source, development and fork builds. + Only the release builds have one; +- with `FIELDCRAFT_TELEMETRY=0` (or `false`, `off`, `no`) in the environment; +- in runs driven by automation (`--control`) and in CI (`CI` set), unless + `FIELDCRAFT_TELEMETRY=1` asks for it; +- `fieldcraft-cli` and the MCP server never send anything themselves; the app counts the + commands they send it. + +## Where it goes + +[PostHog](https://posthog.com) Cloud, EU region (`https://eu.i.posthog.com`), a batch of events +every 30 seconds and when the window closes, on a background thread (the web build uses `fetch`). +A batch that cannot be sent (offline, a slow network) is dropped; at most 1000 events wait. Each +event goes with `$process_person_profile: false` (no person profile is built) and +`$geoip_disable: true` (no location is derived from the connection's IP address). +`FIELDCRAFT_TELEMETRY_HOST` sends to another host instead (to check what is sent against a local +listener). + +## What is sent + +Every event has these properties: + +| Property | Value | +|---|---| +| `distinct_id` | a random id made for this install when you agree (a UUID), kept in the preferences; nothing about the computer or you | +| `app_version` | FieldCraft's version, `0.5.8` | +| `platform` | `desktop` or `web` | +| `os` | `macos`, `linux`, `windows`, or `web` | +| `arch` | `aarch64`, `x86_64`, or `wasm32` | +| `$process_person_profile` | `false` | +| `$geoip_disable` | `true` | + +The events: + +| Event | Properties | When | +|---|---|---| +| `app_started` | — | the app starts with sharing on, or you turn it on | +| `app_closed` | `duration_s`: seconds since `app_started` | the window closes (the page is closed or left) | +| `command_run` | `command`: the command's id (`mesh.tet.generate`); `kind`: `model` or `session`; `duration_ms`; `ok`: whether it succeeded; `error` (when it failed): the class of the error, one of `unknown_command`, `params`, `model`, `geometry`, `mesh`, `file`, `deck`, `solver`, `explicit`, `fluid`, `unavailable`, `internal`; `from`: `ui` (a click, a form, a menu), `palette` (the command search or command line), `script` (a script or the control channel) or `mcp` (an AI agent) | a command ends; a command run by another (a script's steps inside it) is not counted on its own | +| `milestone` | `name`: `design_opened` (a design, mesh or model opened), `meshed` (a mesher made elements), `solved` (a solve finished), `results_viewed` (results shown) | the first time in a run | +| `sample_opened` | `name`: the sample's name (`cantilever`) | a sample opens | +| `form_cancelled` | `command`: the form's command id | a command's form closes without running it | +| `unavailable_clicked` | `command`: the command id; `missing`: what the model lacks, by requirement name (`material`, `step`, …) | a command is clicked before the model has what it needs | + +A `command_run` as sent: + +```json +{"event": "command_run", "distinct_id": "5f0c…-…", "properties": {"command": "solve.run", "kind": "model", "duration_ms": 412, "ok": true, "from": "ui", "app_version": "0.5.8", "platform": "desktop", "os": "macos", "arch": "aarch64", "$process_person_profile": false, "$geoip_disable": true}} +``` + +## For developers + +The events and the policy are `fieldcraft_engine::usage`; the consent question and the settings +are `crates/ui-egui/src/privacy.rs`; the desktop sender is `apps/fieldcraft/src/usage.rs`, the +web one `apps/fieldcraft-web/src/usage.rs`. The PostHog project key is the build-time variable +`FIELDCRAFT_POSTHOG_KEY`, which the release workflow sets from the repository secret +`POSTHOG_KEY`; it is an ingest-only key. A new event or property is added here first. diff --git a/docs/releasing.md b/docs/releasing.md index d110180..e569c6b 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -60,6 +60,13 @@ in the keychain. SmartScreen warns about an unsigned download (More info › Run anyway) until a code-signing certificate is added. +## Usage analytics key + +The release builds (macOS, Linux and Windows jobs) build in the PostHog project key from the +repository secret `POSTHOG_KEY` as `FIELDCRAFT_POSTHOG_KEY` ([privacy.md](privacy.md)). Without the +secret the builds ask nothing and send nothing, as local and fork builds do. The web build on +Pages has no key. + ## Choose and prepare the version 1. Look at the previous published release (`gh release list -R jyviko/fieldcraft`) and the