From cb6236a639f67e4c310d7d67771923525993db8d Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 11:15:40 -0700 Subject: [PATCH] ci(posthog): use readonly key for checks --- .github/workflows/ci.yml | 4 ++-- .github/workflows/posthog-quality.yml | 4 ++-- scripts/ci-workflow.spec.mjs | 30 ++++++++++++++++++++++++++- tools/posthog/README.md | 15 +++++++------- 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10fa471a4..04cb5e57a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -666,12 +666,12 @@ jobs: - name: posthog:sync --plan if: steps.affected.outputs.is_affected == 'yes' env: - POSTHOG_PERSONAL_API_KEY: ${{ secrets.POSTHOG_PERSONAL_API_KEY }} + POSTHOG_PERSONAL_API_KEY: ${{ secrets.POSTHOG_PERSONAL_API_KEY_READONLY }} POSTHOG_HOST: https://us.i.posthog.com POSTHOG_PROJECT_ID: ${{ secrets.POSTHOG_PROJECT_ID }} run: | if [ -z "$POSTHOG_PERSONAL_API_KEY" ]; then - echo "::notice::POSTHOG_PERSONAL_API_KEY not set — soft skip for contributor PRs." + echo "::notice::POSTHOG_PERSONAL_API_KEY_READONLY not set — soft skip for contributor PRs." exit 0 fi npx nx run posthog-tools:sync:plan diff --git a/.github/workflows/posthog-quality.yml b/.github/workflows/posthog-quality.yml index 3adf2c96a..836d7319b 100644 --- a/.github/workflows/posthog-quality.yml +++ b/.github/workflows/posthog-quality.yml @@ -34,7 +34,7 @@ jobs: - run: npm ci - name: Run live telemetry quality check env: - POSTHOG_PERSONAL_API_KEY: ${{ secrets.POSTHOG_PERSONAL_API_KEY }} + POSTHOG_PERSONAL_API_KEY: ${{ secrets.POSTHOG_PERSONAL_API_KEY_READONLY }} POSTHOG_HOST: https://us.i.posthog.com POSTHOG_PROJECT_ID: ${{ secrets.POSTHOG_PROJECT_ID }} QUALITY_DAYS: ${{ github.event.inputs.days || '7' }} @@ -42,7 +42,7 @@ jobs: run: | set -euo pipefail if [ -z "${POSTHOG_PERSONAL_API_KEY:-}" ] || [ -z "${POSTHOG_PROJECT_ID:-}" ]; then - echo "::error::POSTHOG_PERSONAL_API_KEY and POSTHOG_PROJECT_ID Actions secrets are required." + echo "::error::POSTHOG_PERSONAL_API_KEY_READONLY and POSTHOG_PROJECT_ID Actions secrets are required." exit 1 fi npx nx run posthog-tools:quality:live -- --days "$QUALITY_DAYS" --limit-per-event "$QUALITY_LIMIT_PER_EVENT" --require-critical-coverage diff --git a/scripts/ci-workflow.spec.mjs b/scripts/ci-workflow.spec.mjs index 157c042f9..c7dee5094 100644 --- a/scripts/ci-workflow.spec.mjs +++ b/scripts/ci-workflow.spec.mjs @@ -23,6 +23,15 @@ describe('CI workflow', () => { ); } + async function readPostHogSyncPlanJob() { + const workflow = await readWorkflow(); + return workflow.slice(workflow.indexOf(' posthog-sync-plan:')); + } + + async function readPostHogQualityWorkflow() { + return readFile('.github/workflows/posthog-quality.yml', 'utf8'); + } + it('treats nested library files as deploy-relevant changes', async () => { const deployJob = await readDeployJob(); @@ -58,8 +67,27 @@ describe('CI workflow', () => { assert.ok( productionSmokeJob.indexOf('Verify shared LangGraph backend') < - productionSmokeJob.indexOf('npx playwright install --with-deps chromium') + productionSmokeJob.indexOf( + 'npx playwright install --with-deps chromium' + ) + ); + }); + + it('uses the read-only PostHog key for CI drift checks', async () => { + const postHogSyncPlanJob = await readPostHogSyncPlanJob(); + + assert.match( + postHogSyncPlanJob, + /POSTHOG_PERSONAL_API_KEY:\s*\$\{\{\s*secrets\.POSTHOG_PERSONAL_API_KEY_READONLY\s*\}\}/ ); }); + it('uses the read-only PostHog key for scheduled live quality checks', async () => { + const postHogQualityWorkflow = await readPostHogQualityWorkflow(); + + assert.match( + postHogQualityWorkflow, + /POSTHOG_PERSONAL_API_KEY:\s*\$\{\{\s*secrets\.POSTHOG_PERSONAL_API_KEY_READONLY\s*\}\}/ + ); + }); }); diff --git a/tools/posthog/README.md b/tools/posthog/README.md index 8ca1cd24a..5c929fa95 100644 --- a/tools/posthog/README.md +++ b/tools/posthog/README.md @@ -52,13 +52,14 @@ Requires a **Personal API Key** with `dashboard:write`, `insight:write`, `cohort Env vars (see `.env.example` at repo root): -| Variable | Purpose | -| -------------------------- | --------------------------------------------------- | -| `POSTHOG_PERSONAL_API_KEY` | Personal API Key (Bearer) | -| `POSTHOG_HOST` | `https://us.i.posthog.com` (default) or your region | -| `POSTHOG_PROJECT_ID` | Numeric project id (visible in PostHog URL) | - -**CI** uses the same key (write-scoped) for `--plan` only. **Production hardening TODO:** create a read-only Personal API Key for CI and add it as `POSTHOG_PERSONAL_API_KEY_READONLY` in GitHub Actions secrets. Local development continues using the write-scoped key for `--apply` and `--report`. +| Variable | Purpose | +| ----------------------------------- | ------------------------------------------------------------------ | +| `POSTHOG_PERSONAL_API_KEY` | Write-scoped Personal API Key for local `--apply` and `--report` | +| `POSTHOG_PERSONAL_API_KEY_READONLY` | Read-only Personal API Key for CI `--plan` and live quality checks | +| `POSTHOG_HOST` | `https://us.i.posthog.com` (default) or your region | +| `POSTHOG_PROJECT_ID` | Numeric project id (visible in PostHog URL) | + +**CI** maps `POSTHOG_PERSONAL_API_KEY_READONLY` into the tool's `POSTHOG_PERSONAL_API_KEY` environment variable for read-only `--plan` and live quality checks. Local development continues using the write-scoped `POSTHOG_PERSONAL_API_KEY` for `--apply` and `--report`. ## JSON contract