From 2df0fea26f7b77a946ddafee575ffe50f82760f5 Mon Sep 17 00:00:00 2001 From: rumblefrog Date: Sun, 25 Oct 2026 12:35:55 -0400 Subject: [PATCH 1/4] Skip telemetry pings from the docker dev / CI stack (#1604) The dev stack seeds its DB from data.sql, which ships telemetry.enabled = 1 and the live cf-analytics endpoint. Every web request in the dev container runs Telemetry::tickIfDue() at shutdown, so local `./sbpp.sh up`, the E2E workflow and the CI docs-screenshot capture each sent a ping from a throwaway install with a fresh instance_id, polluting the production dataset. dev-prepend.php (the dev container's auto_prepend_file) now defines SBPP_SKIP_TELEMETRY for web SAPIs, the same per-request opt-out /health.php already uses. The CLI is left alone so PHPUnit inside the container keeps exercising tickIfDue() in TelemetryOptOutTest. Claude-Session: https://claude.ai/code/session_019E8y1xAnzfbNHQhAkgFyMm Co-authored-by: Claude (cherry picked from commit 4d09e4d5822d19ba047ee166734952f84de6fc80) --- ARCHITECTURE.md | 6 +- docker/README.md | 2 +- docker/php/dev-prepend.php | 19 +++++ web/includes/Telemetry/Telemetry.php | 5 ++ .../integration/DevPrependTelemetryTest.php | 73 +++++++++++++++++++ 5 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 web/tests/integration/DevPrependTelemetryTest.php diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index c0416e61c..a7b5fb9e3 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -798,7 +798,11 @@ The tick is the issue #1126 contract: calls without flipping the user-facing toggle. The whole `tickIfDue` body is wrapped in `try { … } catch (\Throwable)` -so telemetry can NEVER hard-fail the request. Pings are never +so telemetry can NEVER hard-fail the request. Defining +`SBPP_SKIP_TELEMETRY` before `init.php` skips the tick for that +request: `/health.php` does this, and so does the docker dev stack's +`docker/php/dev-prepend.php` for every web request, which keeps local +dev, E2E, and CI screenshot runs out of the production dataset. Pings are never audit-logged either — a flapping endpoint would otherwise generate `sb_log` noise that scares admins. Only the enable/disable **transitions** are audit-logged (in diff --git a/docker/README.md b/docker/README.md index 6ecbbcf19..e2c351eb5 100644 --- a/docker/README.md +++ b/docker/README.md @@ -58,7 +58,7 @@ Dockerfile php:8.5-apache + pdo_mysql, intl, zip, mbstring, opcach docker-compose.yml web, db, adminer, mailpit docker/php/ web-entrypoint.sh waits for DB, renders config.php, runs composer install, fixes cache perms - dev-prepend.php normalizes HTTP_HOST so init.php's localhost guard accepts :8080 + dev-prepend.php dev-only constants: install-guard bypass + telemetry opt-out (web requests) docker/db-init/ 00-render-schema.sh on first DB init: substitutes {prefix}/{charset}, loads struc/data, seeds admin sbpp.sh thin wrapper around `docker compose` + common dev tasks diff --git a/docker/php/dev-prepend.php b/docker/php/dev-prepend.php index 0d1bffb6f..4e83526c6 100644 --- a/docker/php/dev-prepend.php +++ b/docker/php/dev-prepend.php @@ -28,3 +28,22 @@ if (!defined('SBPP_DEV_KEEP_INSTALL')) { define('SBPP_DEV_KEEP_INSTALL', true); } + +// Keep dev / CI / test traffic out of the production telemetry +// dataset (#1126). The dev stack seeds the DB from `data.sql`, which +// ships `telemetry.enabled = 1` + the live cf-analytics endpoint, so +// without this every `./sbpp.sh up`, every E2E run, and every docs +// screenshot capture in CI would ping the real Worker from a +// throwaway install with a fresh `instance_id`. `SBPP_SKIP_TELEMETRY` +// is the same per-request opt-out `/health.php` uses; it makes +// `Telemetry::tickIfDue()` return before any DB write or network call. +// +// Web SAPIs only: the CLI also loads this file (PHPUnit inside the +// container via `./sbpp.sh test`), and `TelemetryOptOutTest` drives +// `tickIfDue()` directly against a dead endpoint to pin the gate. +// Defining the constant there would short-circuit those tests. The +// PHPUnit bootstrap never requires `init.php`, so no shutdown tick is +// registered under the CLI anyway. +if (PHP_SAPI !== 'cli' && !defined('SBPP_SKIP_TELEMETRY')) { + define('SBPP_SKIP_TELEMETRY', true); +} diff --git a/web/includes/Telemetry/Telemetry.php b/web/includes/Telemetry/Telemetry.php index 75f878ee9..03cf8a83f 100644 --- a/web/includes/Telemetry/Telemetry.php +++ b/web/includes/Telemetry/Telemetry.php @@ -96,6 +96,11 @@ final class Telemetry * cURL POST on every probe, but the per-tick slot-reservation * `UPDATE` on `:prefix_settings.last_ping` is still pure noise * we'd rather not pay on a 2880-probe-per-day endpoint. + * + * The docker dev stack (`docker/php/dev-prepend.php`) defines + * the same constant on every web request, so local dev, E2E, + * and the CI docs-screenshot capture never ping the live + * endpoint. */ public static function tickIfDue(): void { diff --git a/web/tests/integration/DevPrependTelemetryTest.php b/web/tests/integration/DevPrependTelemetryTest.php new file mode 100644 index 000000000..4e8f6b323 --- /dev/null +++ b/web/tests/integration/DevPrependTelemetryTest.php @@ -0,0 +1,73 @@ +prependPath(); + $this->assertFileExists($prepend); + $source = (string) file_get_contents($prepend); + + $this->assertMatchesRegularExpression( + "/if\s*\(\s*PHP_SAPI\s*!==\s*'cli'\s*&&\s*!defined\('SBPP_SKIP_TELEMETRY'\)\s*\)\s*\{\s*" + . "define\('SBPP_SKIP_TELEMETRY',\s*true\);/", + $source, + 'docker/php/dev-prepend.php must define SBPP_SKIP_TELEMETRY for web requests so the ' + . 'dev / E2E / CI stack never pings the live telemetry endpoint.' + ); + + $dockerfile = (string) file_get_contents(ROOT . '../docker/Dockerfile'); + $this->assertStringContainsString( + 'auto_prepend_file=/usr/local/etc/php/dev-prepend.php', + $dockerfile, + 'docker/Dockerfile must keep loading dev-prepend.php via auto_prepend_file.' + ); + } + + public function testDevPrependLeavesCliAlone(): void + { + $code = 'require ' . var_export($this->prependPath(), true) . ';' + . ' echo defined("SBPP_SKIP_TELEMETRY") ? "defined" : "undefined";'; + $output = shell_exec(escapeshellarg(PHP_BINARY) . ' -n -r ' . escapeshellarg($code)); + + $this->assertSame( + 'undefined', + $output, + 'dev-prepend.php must not define SBPP_SKIP_TELEMETRY under the CLI; ' + . 'TelemetryOptOutTest relies on tickIfDue() running inside the container.' + ); + } +} From 4cd876018b728a8dbc47cf15242afcd17a793a69 Mon Sep 17 00:00:00 2001 From: rumblefrog Date: Sun, 25 Oct 2026 12:53:57 -0400 Subject: [PATCH 2/4] fix(telemetry): send the shared-hosting env signals the Worker expects (#1606) sbpp/cf-analytics#4 extended schema-1 with env.sapi, three env doubles (memory_limit_mb, max_execution_time, disable_functions_count) and twelve env bits, and called for a paired panel change. That change never landed, so every ping arrived without them and the Analytics Engine rows carried null / 0 in those slots for every install. Add Sbpp\Telemetry\HostFingerprint, which computes the 16 fields per cf-analytics#3 (each probe Throwable-guarded; open_basedir value, DOCUMENT_ROOT and the disable_functions list reduced to a boolean or count), and merge it into Telemetry::collectEnv(). Extend the vendored JSON Schema and the parity test to match. memory_limit_mb is null on unlimited hosts. The Worker's zod schema rejects an explicit null (whole ping 400s), so send() now drops null leaves and the Worker sees the key as absent, which it maps to a null double as intended. Claude-Session: https://claude.ai/code/session_01Bwe7AmJS69ccciKdc2PPQi Co-authored-by: Claude (cherry picked from commit b67c6cdfc495ffbfdddcb9afad314c75cfed7856) --- AGENTS.md | 2 +- ARCHITECTURE.md | 11 +- docs/src/content/docs/updating/1-8-to-2-0.mdx | 10 +- web/includes/Telemetry/HostFingerprint.php | 182 ++++++++++++++++++ web/includes/Telemetry/Telemetry.php | 26 ++- web/includes/Telemetry/schema-1.lock.json | 92 ++++++++- .../integration/TelemetryCollectTest.php | 19 ++ .../integration/TelemetrySchemaParityTest.php | 16 ++ web/tests/unit/HostFingerprintTest.php | 97 ++++++++++ 9 files changed, 449 insertions(+), 6 deletions(-) create mode 100644 web/includes/Telemetry/HostFingerprint.php create mode 100644 web/tests/unit/HostFingerprintTest.php diff --git a/AGENTS.md b/AGENTS.md index 0fde01985..59d524d1d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4993,7 +4993,7 @@ the spec, target a 1920px viewport, not 1440px. | Render admin-authored Markdown to safe HTML | `web/includes/Markup/IntroRenderer.php` (`Sbpp\Markup`) | | Add a "Generate password" button to a password field (or change how passwords are generated) | Put `data-password-generator` + `data-password-targets="[,]"` on a `