From 318eaa08d6240f3b8a23a20a97f5bd203581eedf Mon Sep 17 00:00:00 2001 From: ivklgn Date: Mon, 7 Sep 2026 09:37:07 +0500 Subject: [PATCH 1/2] refactor: send telemetry to edge.archcore.ai MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ph.archcore.ai is on the list of subdomain prefixes PostHog itself names as targeted by content blockers, alongside analytics, tracking and telemetry. It also spent months dead — its Vercel deployment went away with the landing site's move to GitHub Pages, and every beacon from here got a 404 nobody saw. Both are fixed by moving to a neutral name behind a PostHog managed reverse proxy. defaultEndpoint has no environment override, so a released binary reports to whatever host it was built with, forever. ph.archcore.ai therefore stays alive as a second proxy on the same project until builds carrying this change have propagated; the comment records why, so the next person does not delete it. Not shipped until the CNAMEs answer — see the cutover checklist in analytics-host-must-reach-posthog.adr.md in archcore-ai/landing. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01TJ1vhqTmechbRGqMmhuBEH --- install.ps1 | 2 +- install.sh | 2 +- internal/telemetry/telemetry.go | 8 +++++++- internal/telemetry/telemetry_test.go | 4 ++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/install.ps1 b/install.ps1 index 3d853c6..f23f9d0 100644 --- a/install.ps1 +++ b/install.ps1 @@ -17,7 +17,7 @@ $BINARY_NAME = 'archcore' # PostHog project key rather than comparing against the placeholder text, so the # substitution can never accidentally rewrite its own off-switch. $POSTHOG_KEY = '__POSTHOG_KEY__' -$POSTHOG_HOST = 'https://ph.archcore.ai' +$POSTHOG_HOST = 'https://edge.archcore.ai' # Reported as `$lib_version` alongside `$lib`. The script is fetched fresh on # every run and carries no other version marker, so this is the only way to tell diff --git a/install.sh b/install.sh index 6b9213e..0b2b9dd 100644 --- a/install.sh +++ b/install.sh @@ -27,7 +27,7 @@ PINNED_VERSION="${ARCHCORE_VERSION:-}" # rather than comparing against the placeholder text, so the substitution can # never accidentally rewrite its own off-switch. POSTHOG_KEY="__POSTHOG_KEY__" -POSTHOG_HOST="https://ph.archcore.ai" +POSTHOG_HOST="https://edge.archcore.ai" # Reported as `$lib_version` alongside `$lib`. The script is fetched fresh on # every run and carries no other version marker, so this is the only way to tell diff --git a/internal/telemetry/telemetry.go b/internal/telemetry/telemetry.go index f7965cc..4155392 100644 --- a/internal/telemetry/telemetry.go +++ b/internal/telemetry/telemetry.go @@ -51,7 +51,13 @@ var apiKey string const ( // defaultEndpoint is the PostHog capture path install.sh already posts to. - defaultEndpoint = "https://ph.archcore.ai/i/v0/e/" + // + // This is hardcoded with no environment override, so a released binary + // reports here for the rest of its life. That is why the host it names can + // only be retired once builds carrying the new one have propagated, and + // why ph.archcore.ai is kept alive alongside edge.archcore.ai — see + // analytics-host-must-reach-posthog.adr.md in archcore-ai/landing. + defaultEndpoint = "https://edge.archcore.ai/i/v0/e/" // keyPrefix marks a real PostHog project key. The guard tests the prefix // rather than comparing against a placeholder, so the release substitution diff --git a/internal/telemetry/telemetry_test.go b/internal/telemetry/telemetry_test.go index 2b9abec..8e5e463 100644 --- a/internal/telemetry/telemetry_test.go +++ b/internal/telemetry/telemetry_test.go @@ -147,7 +147,7 @@ func TestFrozenWireIdentifiers(t *testing.T) { got string want string }{ - {"capture endpoint", defaultEndpoint, "https://ph.archcore.ai/i/v0/e/"}, + {"capture endpoint", defaultEndpoint, "https://edge.archcore.ai/i/v0/e/"}, {"$lib", libName, "archcore-cli"}, {"source", sourceCLI, "cli"}, {"project key prefix", keyPrefix, "phc_"}, @@ -753,7 +753,7 @@ func TestNewClient(t *testing.T) { if c.Version != "1.2.3" { t.Errorf("Version = %q, want %q", c.Version, "1.2.3") } - if want := "https://ph.archcore.ai/i/v0/e/"; c.endpoint() != want { + if want := "https://edge.archcore.ai/i/v0/e/"; c.endpoint() != want { t.Errorf("endpoint() = %q, want %q", c.endpoint(), want) } if c.key() != apiKey { From 8160842b1c2128fc55bfe91c47795ba0469acd81 Mon Sep 17 00:00:00 2001 From: ivklgn Date: Mon, 7 Sep 2026 12:25:52 +0500 Subject: [PATCH 2/2] docs: point the telemetry documents at edge.archcore.ai MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three .archcore documents still named ph.archcore.ai as the ingestion host. The beacon ADR also listed "resolves to Vercel, unreliable in Russia" as a hypothetical drawback — that risk had already fired: the deployment behind that host was deleted with the landing site's move to GitHub Pages and every beacon got a 404 for months. Recorded as what happened, with the CI check that now prevents it, rather than as a risk still ahead of us. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01TJ1vhqTmechbRGqMmhuBEH --- .archcore/release/release-infrastructure.doc.md | 2 +- .archcore/telemetry/cli-update-telemetry.spec.md | 2 +- .../telemetry/install-analytics-via-installer-beacon.adr.md | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.archcore/release/release-infrastructure.doc.md b/.archcore/release/release-infrastructure.doc.md index e53bca0..d4ed56c 100644 --- a/.archcore/release/release-infrastructure.doc.md +++ b/.archcore/release/release-infrastructure.doc.md @@ -84,7 +84,7 @@ The contract, the event properties, and the opt-out procedure are in install-scr | `GITHUB_TOKEN` | this repo (automatic) | yes | Publishes the release. GitHub Actions provides it; nothing to configure. | | `LANDING_DISPATCH_TOKEN` | this repo (secret) | no | A PAT with `contents: write` on `archcore-ai/landing`, used by `notify-landing.yml`. While it is absent, the job emits a warning and exits 0 — archcore.ai still picks the installer up on its next deploy, so a missing secret never turns a CLI push red. | | `POSTHOG_KEY` | `archcore-ai/landing` (variable) | yes, for analytics | Public PostHog project key. Substituted into the installers at landing deploy time. A missing or non-`phc_` value fails the landing deploy. | -| `POSTHOG_HOST` | `archcore-ai/landing` (variable) | no | Ingestion host, `https://ph.archcore.ai`. Falls back to the same value when unset. | +| `POSTHOG_HOST` | `archcore-ai/landing` (variable) | no | Ingestion host, `https://edge.archcore.ai`. Falls back to the same value when unset. | The pipeline needs no signing keys and no notarization credentials. diff --git a/.archcore/telemetry/cli-update-telemetry.spec.md b/.archcore/telemetry/cli-update-telemetry.spec.md index 6576eb1..bc12f1e 100644 --- a/.archcore/telemetry/cli-update-telemetry.spec.md +++ b/.archcore/telemetry/cli-update-telemetry.spec.md @@ -17,7 +17,7 @@ Out of scope: the installer beacon's own events and payload, and every CLI event - Command surface: `archcore update` and `archcore update --check` — `@cmd/update.go`. In this release, the unattended path carries no flag; its only caller is the MCP trigger. - Sender: `internal/telemetry` [planned] — a package-level key variable, populated by `-X` ldflags in `@.goreleaser.yaml` at release, empty in every other build. -- Endpoint: `POST https://ph.archcore.ai/i/v0/e/`, PostHog capture payload — the endpoint `send_event()` in `@install.sh` already uses. +- Endpoint: `POST https://edge.archcore.ai/i/v0/e/`, PostHog capture payload — the endpoint `send_event()` in `@install.sh` already uses. Builds released before that change report to `https://ph.archcore.ai/i/v0/e/`, which is kept alive for them. - Events: `cli_updated`, `cli_update_failed`, `cli_update_skipped`. - Identifier: `${XDG_STATE_HOME:-$HOME/.local/state}/archcore/install-id`, the path `install_id_path()` in `@install.sh` writes and `updateCheckCachePath()` in `@cmd/update.go` mirrors. - Stage categories on `cli_update_failed`, derived from the failure points of `@internal/update/update.go`: `check`, `download`, `checksum`, `extract`, `replace`. diff --git a/.archcore/telemetry/install-analytics-via-installer-beacon.adr.md b/.archcore/telemetry/install-analytics-via-installer-beacon.adr.md index bc3554b..4521777 100644 --- a/.archcore/telemetry/install-analytics-via-installer-beacon.adr.md +++ b/.archcore/telemetry/install-analytics-via-installer-beacon.adr.md @@ -30,7 +30,7 @@ An npm distribution channel was proposed first, on the theory that npm download Measure installs in two independent ways, both reporting into the PostHog project that already serves archcore.ai. -1. **An installer beacon.** `install.sh` and `install.ps1` send one anonymous event per run — `cli_installed` on success, `cli_install_failed` with a `stage` category on failure — to `https://ph.archcore.ai/i/v0/e/`, the existing first-party ingestion proxy. +1. **An installer beacon.** `install.sh` and `install.ps1` send one anonymous event per run — `cli_installed` on success, `cli_install_failed` with a `stage` category on failure — to `https://edge.archcore.ai/i/v0/e/`, the first-party ingestion proxy. 2. **A release-counter bridge.** `.github/workflows/install-stats.yml` in `archcore-ai/landing` reports the GitHub `download_count` totals to PostHog on a daily schedule, sending both the denoised `checksums.txt` figure and the raw archive figure so the noise floor stays visible on the same chart. The two measure different populations — the bridge counts asset fetches including automation, the beacon counts consenting machines — so each is the other's sanity check. @@ -71,7 +71,7 @@ Honest, and useless for this metric — install-time opt-in rates are near zero. ### Alternative 4: An endpoint on archcore.ai that forwards to PostHog -Not available. archcore.ai is static GitHub Pages with no server, no edge functions, and no middleware, per the hosting ADR. The existing `ph.archcore.ai` proxy is the only first-party ingestion path. +Not available. archcore.ai is static GitHub Pages with no server, no edge functions, and no middleware, per the hosting ADR. The `edge.archcore.ai` proxy is the only first-party ingestion path. ## Consequences @@ -85,7 +85,7 @@ Not available. archcore.ai is static GitHub Pages with no server, no edge functi ### Negative - **A published privacy promise was reversed.** `/privacy` previously stated "No telemetry. We do not collect usage analytics, crash reports, identifiers, or any data from the plugin or CLI." The page now scopes that promise to the installed tools and documents the installer beacon separately. Any future change to what the beacon sends must update that copy in the same change. -- `ph.archcore.ai` resolves to Vercel, the platform the project migrated away from because it is unreliable in Russia. Beacons from affected users fail silently, so install geography is systematically skewed. The bridge is unaffected and partly compensates. +- The proxy is a single point of silent failure, and this was not hypothetical: `ph.archcore.ai` pointed at a Vercel deployment that the landing site's move to GitHub Pages deleted, and every beacon got a 404 for months without anything failing. It now runs on `edge.archcore.ai`, a PostHog managed reverse proxy behind Cloudflare, and CI proves the host reachable before a deploy ships against it. Cloudflare is more reachable from Russia than Vercel was, but not guaranteed, so install geography can still skew. The bridge is unaffected and partly compensates. See `analytics-host-must-reach-posthog.adr.md` in `archcore-ai/landing`. - The beacon adds up to 3 s to an install on a network where the proxy is unreachable. - The bridge's historical mode reports a per-release total *as of the day it runs*. The GitHub API exposes no historical series, so a true daily backfill is impossible; charted at publish dates it answers "which releases got picked up", not "installs per week". - **A red or flaky `Install Smoke` run now blocks installer publication**, where previously the redeploy fired regardless. `Notify Landing` keeps its `workflow_dispatch` as the manual override.