From 9b106a9f6ea7237146d8b3373a826f679f044f3f Mon Sep 17 00:00:00 2001 From: Agent Date: Thu, 1 Oct 2026 20:29:47 +0000 Subject: [PATCH 1/2] fix(telemetry): use the stable pgflow collector endpoint The 0.17.1 CLI and database senders use a workers.dev hostname without the account subdomain, so telemetry cannot reach the collector. Point both senders at telemetry.pgflow.dev while preserving failure handling and privacy safeguards. Add a generated function-only upgrade migration without rewriting released history or enabling telemetry for opted-out databases. Update regression checks, upgrade guidance, and patch changesets. Collector deployment and release remain separate prerequisites tracked in #691. --- .changeset/fix-telemetry-endpoint.md | 6 ++ apps/telemetry-worker/src/index.test.ts | 4 +- .../install/report-install-telemetry.test.ts | 2 +- .../install/report-install-telemetry.ts | 2 +- pkgs/core/schemas/0135_function_report.sql | 2 +- ...01194538_pgflow_telemetry_endpoint_fix.sql | 74 +++++++++++++++++++ pkgs/core/supabase/migrations/atlas.sum | 3 +- .../supabase/tests/telemetry/report.test.sql | 6 +- .../src/content/docs/reference/telemetry.mdx | 12 ++- 9 files changed, 101 insertions(+), 10 deletions(-) create mode 100644 .changeset/fix-telemetry-endpoint.md create mode 100644 pkgs/core/supabase/migrations/20261001194538_pgflow_telemetry_endpoint_fix.sql diff --git a/.changeset/fix-telemetry-endpoint.md b/.changeset/fix-telemetry-endpoint.md new file mode 100644 index 000000000..190a6ae06 --- /dev/null +++ b/.changeset/fix-telemetry-endpoint.md @@ -0,0 +1,6 @@ +--- +"@pgflow/core": patch +"pgflow": patch +--- + +Fix the telemetry endpoint: send CLI install events and daily database reports to `https://telemetry.pgflow.dev` instead of the unresolvable `pgflow-telemetry.workers.dev` hostname. Existing 0.17.1 installations get the corrected `pgflow_telemetry.report()` function through a new additive migration; opted-out databases stay opted out. Patch-level behavior: opt-out flags, local/CI suppression, timeouts, and payload limits are unchanged. diff --git a/apps/telemetry-worker/src/index.test.ts b/apps/telemetry-worker/src/index.test.ts index be87c9bc2..60eb5428c 100644 --- a/apps/telemetry-worker/src/index.test.ts +++ b/apps/telemetry-worker/src/index.test.ts @@ -24,7 +24,7 @@ function post( contentType = 'application/json', ): Promise { return worker.fetch( - new Request('https://pgflow-telemetry.workers.dev/', { + new Request('https://telemetry.pgflow.dev/', { method: 'POST', headers: { 'content-type': contentType }, body: raw ? (body as string) : JSON.stringify(body), @@ -92,7 +92,7 @@ describe('telemetry ingest', () => { it('rejects GET with 405', async () => { const { env } = makeEnv(); const res = await worker.fetch( - new Request('https://pgflow-telemetry.workers.dev/', { method: 'GET' }), + new Request('https://telemetry.pgflow.dev/', { method: 'GET' }), env, ); expect(res.status).toBe(405); diff --git a/pkgs/cli/__tests__/commands/install/report-install-telemetry.test.ts b/pkgs/cli/__tests__/commands/install/report-install-telemetry.test.ts index 88185dbef..4d3f675f9 100644 --- a/pkgs/cli/__tests__/commands/install/report-install-telemetry.test.ts +++ b/pkgs/cli/__tests__/commands/install/report-install-telemetry.test.ts @@ -18,7 +18,7 @@ describe('reportInstallTelemetry', () => { expect(send).toHaveBeenCalledOnce(); const [url, request] = send.mock.calls[0] as [string, RequestInit]; - expect(url).toBe('https://pgflow-telemetry.workers.dev/'); + expect(url).toBe('https://telemetry.pgflow.dev/'); expect(request.method).toBe('POST'); expect(JSON.parse(request.body as string)).toEqual({ schema: 1, diff --git a/pkgs/cli/src/commands/install/report-install-telemetry.ts b/pkgs/cli/src/commands/install/report-install-telemetry.ts index 473a3e42a..3d80dcace 100644 --- a/pkgs/cli/src/commands/install/report-install-telemetry.ts +++ b/pkgs/cli/src/commands/install/report-install-telemetry.ts @@ -1,7 +1,7 @@ import { getVersion } from '../../utils/get-version.js'; import type { MigrationInstallResult } from './copy-migrations.js'; -const ENDPOINT = 'https://pgflow-telemetry.workers.dev/'; +const ENDPOINT = 'https://telemetry.pgflow.dev/'; const SEMVER_RE = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/; type Environment = Record; diff --git a/pkgs/core/schemas/0135_function_report.sql b/pkgs/core/schemas/0135_function_report.sql index 2d94b7afa..d87726e14 100644 --- a/pkgs/core/schemas/0135_function_report.sql +++ b/pkgs/core/schemas/0135_function_report.sql @@ -66,7 +66,7 @@ begin -- prune rolls the queued request back with everything else in this block. begin v_request_id := net.http_post( - url => 'https://pgflow-telemetry.workers.dev', + url => 'https://telemetry.pgflow.dev', body => v_payload, headers => jsonb_build_object('Content-Type', 'application/json'), timeout_milliseconds => 5000 diff --git a/pkgs/core/supabase/migrations/20261001194538_pgflow_telemetry_endpoint_fix.sql b/pkgs/core/supabase/migrations/20261001194538_pgflow_telemetry_endpoint_fix.sql new file mode 100644 index 000000000..b28c4c4d4 --- /dev/null +++ b/pkgs/core/supabase/migrations/20261001194538_pgflow_telemetry_endpoint_fix.sql @@ -0,0 +1,74 @@ +-- Modify "report" function +CREATE OR REPLACE FUNCTION "pgflow_telemetry"."report" () RETURNS text LANGUAGE plpgsql SET "search_path" = '' AS $$ +declare + v_day date := current_date - 1; + v_payload jsonb; + v_request_id bigint; +begin + -- Every failure path, including the gates below, returns a status: + -- query cancellation (SQLSTATE 57014) or any gate failure must not + -- escape this function (regression-tested in report.test.sql). + begin + if not exists ( + select 1 from pgflow.runs r + where r.started_at >= v_day and r.started_at < v_day + 1 + ) then + return 'skipped: inactive day'; + end if; + + if exists ( + select 1 from pgflow_telemetry.sent_reports s where s.day = v_day + ) then + return 'skipped: already reported'; + end if; + + -- Local CLI stack (developer machines, CI on supabase start) never sends. + if pgflow.is_local() then + return 'skipped: local'; + end if; + exception + when query_canceled then + return 'error: canceled'; + when others then + return 'error: gate failed'; + end; + + begin + v_payload := pgflow_telemetry.build_payload(v_day); + -- build_payload enforces the receiver's limits; this guard keeps a + -- future regression from queueing a body the receiver would reject. + if jsonb_array_length(v_payload->'contributions') > 64 + or octet_length(v_payload::text) > 2048 then + return 'error: build failed'; + end if; + exception + when query_canceled then + return 'error: canceled'; + when others then + return 'error: build failed'; + end; + + -- pg_net queues transactionally: a failure in the audit insert or the + -- prune rolls the queued request back with everything else in this block. + begin + v_request_id := net.http_post( + url => 'https://telemetry.pgflow.dev', + body => v_payload, + headers => jsonb_build_object('Content-Type', 'application/json'), + timeout_milliseconds => 5000 + ); + insert into pgflow_telemetry.sent_reports (day, payload, request_id) + values (v_day, v_payload, v_request_id); + + delete from pgflow_telemetry.sent_reports + where day < current_date - 90; + exception + when query_canceled then + return 'error: canceled'; + when others then + return 'error: send failed'; + end; + + return 'sent: ' || v_request_id; +end +$$; diff --git a/pkgs/core/supabase/migrations/atlas.sum b/pkgs/core/supabase/migrations/atlas.sum index 9480fdac3..771681f6d 100644 --- a/pkgs/core/supabase/migrations/atlas.sum +++ b/pkgs/core/supabase/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:QaPHwmoNhloJfusgYJ5s9h5QS7cwqav//Qak1RSWtDw= +h1:sOlj3XB6+Tss5pInYvuwe/yxDhipAVGK/ZF49e+gLAo= 20250429164909_pgflow_initial.sql h1:I3n/tQIg5Q5nLg7RDoU3BzqHvFVjmumQxVNbXTPG15s= 20250517072017_pgflow_fix_poll_for_tasks_to_use_separate_statement_for_polling.sql h1:wTuXuwMxVniCr3ONCpodpVWJcHktoQZIbqMZ3sUHKMY= 20250609105135_pgflow_add_start_tasks_and_started_status.sql h1:ggGanW4Wyt8Kv6TWjnZ00/qVb3sm+/eFVDjGfT8qyPg= @@ -25,3 +25,4 @@ h1:QaPHwmoNhloJfusgYJ5s9h5QS7cwqav//Qak1RSWtDw= 20260915074120_pgflow_private_step_queues.sql h1:+vsfsOyDaM8WISO/jxp4UBlTzPuQhg6RwBCiOAk5YAE= 20260919152659_pgflow_fix_force_skip_multi_queue.sql h1:DC7uQRwjpOm3A4NtDYb3SjbSSqQix4MlQ4+BnhGQ/Q8= 20260920093533_pgflow_telemetry.sql h1:dp3gNRSUpTNoPH9w4n9P7CBhXFoJspQ+hP9MfQ1u3Fg= +20261001194538_pgflow_telemetry_endpoint_fix.sql h1:/ROJiUnHzOt6xsw9f877wY6KlrVOFxpjGBuFuweX6Is= diff --git a/pkgs/core/supabase/tests/telemetry/report.test.sql b/pkgs/core/supabase/tests/telemetry/report.test.sql index f3ce69b1a..0a1c2ac32 100644 --- a/pkgs/core/supabase/tests/telemetry/report.test.sql +++ b/pkgs/core/supabase/tests/telemetry/report.test.sql @@ -78,7 +78,7 @@ select is( select is( ( select count(*) from net.http_request_queue q - where q.url like 'https://pgflow-telemetry.workers.dev%' + where q.url like 'https://telemetry.pgflow.dev%' and convert_from(q.body, 'UTF8')::jsonb = pgflow_telemetry.preview(current_date - 1) ), 1::bigint, @@ -117,7 +117,7 @@ select is( select is( ( select count(*) from net.http_request_queue - where url like 'https://pgflow-telemetry.workers.dev%' + where url like 'https://telemetry.pgflow.dev%' ), 0::bigint, 'failed send rolls the pg_net queue insert back' @@ -266,7 +266,7 @@ select is( select is( ( select count(*) from net.http_request_queue - where url like 'https://pgflow-telemetry.workers.dev%' + where url like 'https://telemetry.pgflow.dev%' ), 0::bigint, 'ordinary gate failure queues no HTTP request' diff --git a/pkgs/website/src/content/docs/reference/telemetry.mdx b/pkgs/website/src/content/docs/reference/telemetry.mdx index 9517b9954..35b6c48ff 100644 --- a/pkgs/website/src/content/docs/reference/telemetry.mdx +++ b/pkgs/website/src/content/docs/reference/telemetry.mdx @@ -115,9 +115,19 @@ select pgflow_telemetry.enable(); -- resume daily database reports Local development databases (`supabase start`) never send telemetry, and days without runs send nothing. The scheduled job bounds itself with a 5-second statement timeout. If you call `pgflow_telemetry.report()` manually, set your own `statement_timeout` first: PostgreSQL cannot arm a timeout that changes inside a running function. +## Upgrading from 0.17.1 + +pgflow 0.17.1 pointed both senders at `https://pgflow-telemetry.workers.dev`, a hostname that does not resolve. The corrected endpoint is `https://telemetry.pgflow.dev`. + +- Databases get the corrected daily sender through a new pgflow migration that replaces `pgflow_telemetry.report()`. Update pgflow, rerun `pgflow install` to copy the migration, and apply it with your normal Supabase migration workflow. Databases that disabled telemetry stay disabled: the migration replaces only the function and never reschedules the reporting job. +- The corrected installation sender ships in the pgflow CLI patch release; update the CLI you invoke (`npx pgflow@latest`). +- Deploying the collector endpoint alone does not repair 0.17.1: its senders keep using the unresolvable hostname until you upgrade. + +Until the upgrade lands, daily reports from 0.17.1 databases fail silently, exactly like any other send failure: no error surfaces in your database, and the day is not retried. + ## Privacy -- Both signals go to `https://pgflow-telemetry.workers.dev`, a [Cloudflare Worker](https://workers.cloudflare.com/) backed by [Workers Analytics Engine](https://developers.cloudflare.com/analytics/analytics-engine/). +- Both signals go to `https://telemetry.pgflow.dev`, a [Cloudflare Worker](https://workers.cloudflare.com/) backed by [Workers Analytics Engine](https://developers.cloudflare.com/analytics/analytics-engine/). - Stored data expires automatically after three months. - The `sent_reports` audit table in your database is separate: its 90-day prune runs only as part of a successful daily report. Disabled databases and databases with inactive days keep their audit rows until you delete them yourself. - pgflow sends no identifiers and the telemetry application stores no transport metadata, including IP addresses or user agents. Cloudflare necessarily processes source IPs to carry the network request. From a99d146111c530255ebeaaadaf350c1ce04b6f7d Mon Sep 17 00:00:00 2001 From: Agent Date: Fri, 2 Oct 2026 07:43:12 +0200 Subject: [PATCH 2/2] docs(telemetry): drop silent-failure paragraph from upgrade note Review feedback: the sentence restated existing failure behavior and added no upgrade guidance. The upgrade bullets already cover the migration, CLI update, and endpoint prerequisite. --- pkgs/website/src/content/docs/reference/telemetry.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkgs/website/src/content/docs/reference/telemetry.mdx b/pkgs/website/src/content/docs/reference/telemetry.mdx index 35b6c48ff..b10b37f5e 100644 --- a/pkgs/website/src/content/docs/reference/telemetry.mdx +++ b/pkgs/website/src/content/docs/reference/telemetry.mdx @@ -123,8 +123,6 @@ pgflow 0.17.1 pointed both senders at `https://pgflow-telemetry.workers.dev`, a - The corrected installation sender ships in the pgflow CLI patch release; update the CLI you invoke (`npx pgflow@latest`). - Deploying the collector endpoint alone does not repair 0.17.1: its senders keep using the unresolvable hostname until you upgrade. -Until the upgrade lands, daily reports from 0.17.1 databases fail silently, exactly like any other send failure: no error surfaces in your database, and the day is not retried. - ## Privacy - Both signals go to `https://telemetry.pgflow.dev`, a [Cloudflare Worker](https://workers.cloudflare.com/) backed by [Workers Analytics Engine](https://developers.cloudflare.com/analytics/analytics-engine/).