Skip to content

Stop deploying the collector from calibration-diagnostics on push - #233

Merged
anth-volk merged 2 commits into
mainfrom
collector-deploy-switch
Oct 20, 2026
Merged

anth-volk merged 2 commits into
mainfrom
collector-deploy-switch

Conversation

@anth-volk

@anth-volk anth-volk commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #231

Summary

PolicyEngine/microcosm-collector releases and deploys the collector from its own repo once its repo variable COLLECTOR_DEPLOY_ENABLED is true. Both repos deploy the same Cloud Run services and database, so this repo has to stop deploying first.

After this PR, .github/workflows/telemetry-service.yml:

  • No longer runs on push. Merging a change to telemetry-service/ deploys nothing. Merging this PR doesn't run it either: GitHub reads the workflow from the merge commit, which has no push trigger.
  • Still validates pull requests that change telemetry-service/ or the workflow (tests, migration check, Ruff, image build), for example calibration-diagnostics#219.
  • Keeps one manual redeploy of main (staging, then production, exactly as before) for rolling the switch back. Every dispatch first runs the job "Require the microcosm-collector deployment to be off", and deploy-staging needs it. That job fails, so a refused dispatch is a failed run rather than a green run with both deployment jobs skipped, when:
    • the dispatch isn't from main, or the person dispatching didn't check collector_deploy_disabled (confirming that COLLECTOR_DEPLOY_ENABLED is unset in microcosm-collector); or
    • any microcosm-collector Push run is queued, waiting or in progress. telemetry-service/scripts/require-collector-push-runs-finished.sh lists them through GitHub's REST API with the workflow's own token; microcosm-collector is public. It can't read that repo's variables, so the variable stays a person's confirmation.

Limiting the workflow to confirmed manual runs is safer than deleting it now: until the first deployment from microcosm-collector is verified, this keeps a deployment path that has already deployed production from this repo. calibration-diagnostics#234 (stacked on this PR) removes the workflow and telemetry-service/ after the switch.

telemetry-service/README.md now says where the collector is deployed from, and the existing workflow test expects one path filter instead of two.

Switch-over runbook

People needed: an admin of microcosm-collector (to set a repo variable), someone who can merge in both repos, and read access to the policyengine-api Google Cloud project for the checks.

From step 3 until step 6 is done, merge nothing else to microcosm-collector's main. Each merge there starts two Push runs: one on the merge commit, which commits the release ("Update microcosm-collector"), then one on that release commit, which verifies it, tags it and then reaches the deployment jobs. Those jobs read COLLECTOR_DEPLOY_ENABLED only when the run reaches them, and runs can wait a long time for runners: the run for microcosm-collector#9's release commit eac60d4 was created at 21:17 UTC on 2026-10-19 and reached its deployment jobs at 22:22. So a release run that is still unfinished when step 4 sets the variable deploys microcosm-collector's main as of that release commit, without #11, to staging and then production.

0. Before starting

  1. Done (path A). calibration-diagnostics#219 merged on 2026-10-20 at 13:29 UTC (a213d48). Its "Telemetry service" run 39430883831 deployed it to staging and production by 15:54 UTC, so the first deployment from microcosm-collector changes only where the deploy runs.

  2. Collector PRs that wait for the verified switch-over (after step 6). They change the database or the deployment, and from step 5 on, every merge to microcosm-collector's main is a production deployment:

    • microcosm-collector#12 adds migration 0002_artifact_intake. Deployed before the switch is verified, it would also change the schema, and the rollback to calibration-diagnostics below would stop working (its older code can't upgrade a database at a revision it doesn't know).
    • The collector's own Hugging Face read token and its daily check (plan items S1 and S2): a secret mount and a schedule.
    • ES256 session-token signing (plan item C5): a new secret.
    • The move to a dedicated Google Cloud project and domain (plan items C2 and C3).

    Open on 2026-10-20: microcosm-collector#12 (stacked on Target inventory: surface all 5 storage tiers across the calibration pipeline #6), Reform validation: populace estimates vs JCT scores #16 (C5) and Fix nonsense relative error on zero-target rows #18 and Show calibration loss honestly: scientific format + $0-target breakdown #19 (S1 and S2), plus Target inventory: surface all 5 storage tiers across the calibration pipeline #6 (the submission contract) and Add Modal deployment for the FastAPI backend #21 (the project and domain decision memo), which are documentation only. All of them wait until step 6 is done, because any merge starts a release run. Already merged: microcosm-collector#9 adds telemetry_collector/orrery/projection/, which no service module imports and which adds no dependency, so it is in the image but doesn't change what the service runs.

  3. Record the production service's current state (read-only; repeat with staging for the staging service):

collector_variable() { gh api "repos/PolicyEngine/microcosm-collector/environments/production/variables/$1" --jq .value; }
PROJECT_ID=$(collector_variable TELEMETRY_GCP_PROJECT_ID)
REGION=$(collector_variable TELEMETRY_GCP_REGION)
SERVICE=$(collector_variable TELEMETRY_SERVICE)
MIGRATION_JOB=$(collector_variable TELEMETRY_MIGRATION_JOB)
CLOUD_SQL_CONNECTION=$(collector_variable TELEMETRY_CLOUD_SQL_CONNECTION)
gcloud run services describe "$SERVICE" --project "$PROJECT_ID" --region "$REGION" --format=json \
  | jq '{url: .status.url,
         serving: [.status.traffic[] | select(.percent > 0) | .revisionName],
         image: .spec.template.spec.containers[0].image,
         cloud_sql: .spec.template.metadata.annotations["run.googleapis.com/cloudsql-instances"],
         secrets: [.spec.template.spec.containers[0].env[] | select(.valueFrom) | "\(.name)=\(.valueFrom.secretKeyRef.name)"]}'

Keep the output. serving is the revision to roll back to. On 2026-10-20 at 16:45 UTC, production serves revision microcosm-telemetry-00024-req and staging serves microcosm-telemetry-staging-00023-pig. Both run the image built from calibration-diagnostics a213d48 (#219's merge commit).

Steps

  1. Done. Path A only: merge calibration-diagnostics#219 with "Create a merge commit". telemetry-service.yml deploys it to staging, then production. Wait for both jobs to succeed.

  2. Done. Merge microcosm-collector#4, then check what the first deployment from microcosm-collector will change.

    • microcosm-collector#4 changes only the extraction tool, its tests and documentation. microcosm-collector's ruleset requires a PR branch to be up to date with main, so use "Update branch" if GitHub asks, wait for "Required checks", then merge with "Create a merge commit". Its two Push runs released 0.3.1 (release commit de126c7) and skipped both deployment jobs, because the variable is unset. Done on 2026-10-20: merged at 14:15 UTC (6017fda); runs 39437629029 and 39444932581 succeeded.
    • If Apply each telemetry event batch incrementally instead of re-materializing the run #219 gained commits after 3dfbe05, microcosm-collector#11 must be updated first.
    • In a microcosm-collector checkout, compare the runtime files of the code production runs with microcosm-collector#11 merged into main (path A: calibration-diagnostics main; path B: Apply each telemetry event batch incrementally instead of re-materializing the run #219's head instead):
    git fetch origin main claude/carry-over-incremental-batch-apply
    git fetch https://github.com/PolicyEngine/calibration-diagnostics.git main:refs/switch/production-source
    first_deploy_tree=$(git merge-tree --write-tree origin/main origin/claude/carry-over-incremental-batch-apply)
    git diff --stat refs/switch/production-source:telemetry-service "$first_deploy_tree" \
      -- telemetry_collector migrations alembic.ini Dockerfile pyproject.toml uv.lock

    Expected today: only the files under telemetry_collector/orrery/ (from microcosm-collector#9), pyproject.toml and uv.lock (the package's own name, version and license; no dependency changes), and nothing under migrations/. Review anything else before going on, or hold the PR that added it. The image copies only pyproject.toml, uv.lock, alembic.ini, migrations and telemetry_collector; .dockerignore differs only by excluding more non-runtime files.

  3. Wait until no microcosm-collector Push run is queued or in progress, then merge this PR. In bash or zsh:

    collector_push_runs_finished() {
      local main_head
      main_head=$(gh api repos/PolicyEngine/microcosm-collector/commits/main --jq .sha) || return 1
      gh run list -R PolicyEngine/microcosm-collector --workflow push.yaml --limit 50 \
          --json databaseId,headSha,status \
        | jq -e --arg main_head "$main_head" '
            ([.[] | select(.status != "completed")] | length == 0)
            and any(.[]; .headSha == $main_head and .status == "completed")'
    }
    until collector_push_runs_finished; do sleep 60; done

    It prints false every minute, then true once no Push run is queued, waiting or in progress and the run for main's current head has completed. The second condition covers the moment after a merge commit's run has pushed its release commit and before GitHub has created that commit's run. After microcosm-collector#4, main's head is its release commit, so the check passes only once that commit's run (verify, tag, deployment jobs skipped) has completed. Checked on 2026-10-20 at 16:48 UTC: it prints true for main at de126c7.

    From here until step 6 is done, merge nothing else to microcosm-collector's main.

    Then:

    • Merge this PR only when no calibration-diagnostics CI or "Deploy production" run for main is queued or in progress (gh run list -R PolicyEngine/calibration-diagnostics --branch main --limit 5). Merging deploys the dashboard to production like any merge here. CI cancels an unfinished main CI run when a newer commit reaches main (cancel-in-progress on ci-refs/heads/main), and that commit's dashboard deploy is then skipped. Then wait for the "Deploy production" run for this merge to succeed. telemetry-service.yml doesn't run: gh run list -R PolicyEngine/calibration-diagnostics --workflow telemetry-service.yml --limit 3 shows no run for the merge commit.
    • Use "Update branch" on microcosm-collector#11, which is behind main after step 2, and wait for its "Required checks" to pass. That runs only the pull-request checks, not the Push workflow, and doing it now keeps steps 4 and 5 to about a minute.
    • If steps 4–6 come later, nothing may merge to microcosm-collector's main in the meantime. If something does merge before step 4, its release runs skip both deployment jobs, because the variable is still unset; repeat step 3's wait and the "Update branch" on Add stable diagnostics API layer #11 before going on.
  4. Run collector_push_runs_finished once more. Only if it prints true, set COLLECTOR_DEPLOY_ENABLED to true as a repository variable of microcosm-collector, not in its staging or production environment (Settings → Secrets and variables → Actions → Variables → Repository variables, or gh variable set COLLECTOR_DEPLOY_ENABLED --repo PolicyEngine/microcosm-collector --body true). The deployment jobs read it in their job-level if:, which GitHub evaluates before it loads the job's environment, so a value set only in an environment would leave both jobs skipped. The value must be exactly true.

  5. Straight away, merge microcosm-collector#11 with "Create a merge commit". If GitHub says its branch is out of date, something merged to main after step 3: delete COLLECTOR_DEPLOY_ENABLED and find out which run deployed before going on. Add stable diagnostics API layer #11's Push workflow runs twice: the merge commit's run commits the release ("Update microcosm-collector"), and the release commit's run verifies it, tags it, deploys staging and then, only if staging succeeds, production. Production takes a Cloud SQL backup before its migration step, which is a no-op because the schema doesn't change.

    Then find Add stable diagnostics API layer #11's release commit. Verification needs its SHA, because every release commit has the same message:

    merge_commit=$(gh pr view 11 -R PolicyEngine/microcosm-collector --json mergeCommit --jq .mergeCommit.oid)
    release_commit=$(gh api "repos/PolicyEngine/microcosm-collector/commits?sha=main&per_page=20" \
      | jq -r --arg merge_commit "$merge_commit" \
          '.[] | select(.parents[0].sha == $merge_commit and .commit.message == "Update microcosm-collector") | .sha')
    echo "$release_commit"
    gh run list -R PolicyEngine/microcosm-collector --workflow push.yaml --commit "$release_commit" \
      --json databaseId,status,conclusion,url

    release_commit is empty until the merge commit's run has pushed the release; repeat until it prints one SHA. Checked against microcosm-collector#9: merge commit 5cf1898 gives release commit eac60d4 and run 39326286725. Wait for that run's two deployment jobs to succeed.

  6. Verify (next section), for staging and production.

  7. After a few normal builds, mark calibration-diagnostics#234 ready, change its base to main by hand (this repo doesn't delete merged branches, so GitHub won't retarget it), and merge it. In path B, close calibration-diagnostics#219 unmerged.

Which repo can deploy, at each point

Interval calibration-diagnostics microcosm-collector
Until step 3 on every push to main that changes telemetry-service/ no: release runs tag, then skip both deployment jobs
Steps 3 to 4 only a confirmed manual dispatch from main, while no microcosm-collector Push run is unfinished no
Steps 4 to 5 (about a minute) only a confirmed manual dispatch, which nobody may confirm now any release run that reaches its deployment jobs: steps 3 and 4 make sure none is unfinished, and nothing else merges
After step 5, until calibration-diagnostics#234 only a confirmed manual dispatch from main, for rollback, after unsetting the variable and while no microcosm-collector Push run is unfinished on every merge to main
After calibration-diagnostics#234 none on every merge to main

At every point at least one repo can deploy. Both could deploy at once only if someone confirmed a dispatch while the variable was set, and a microcosm-collector release run reached its deployment jobs after the dispatch's run check had passed.

Verify the deployment from microcosm-collector

Re-run the gcloud run services describe command from step 0 and compare:

  • Same address: url is unchanged. The deployment updated the same Cloud Run service, so the address that Microcosm's delivery service and the dashboard (MICROCOSM_TELEMETRY_COLLECTOR_URL) use still points at it.
  • Add stable diagnostics API layer #11's code: serving is a new revision, and image ends in :$release_commit, the SHA from step 5. The image tag is the GITHUB_SHA of the run that built it. A tag whose commit message is "Update microcosm-collector" proves nothing more: every release commit has that message.
  • Same database and secrets: cloud_sql equals the step 0 value and $CLOUD_SQL_CONNECTION; secrets lists the same three Secret Manager names.
  • Ready: curl -fsS "URL/ready" succeeds. /ready needs a database connection at the current Alembic revision.
  • Migration job: gcloud run jobs executions list --job "$MIGRATION_JOB" --project "$PROJECT_ID" --region "$REGION" --limit 1 shows a successful execution.
  • Backup (production): gcloud sql backups list --instance "${CLOUD_SQL_CONNECTION##*:}" --project "$PROJECT_ID" --limit 1 shows telemetry-release-RUN_ID-ATTEMPT, where RUN_ID is the databaseId that step 5's gh run list --commit "$release_commit" printed.
  • Data continuity: the dashboard's Build progress tab (/microcosm/staging?view=progress) still lists runs registered before the switch, and the next Microcosm build's events appear there.

Roll back

Situation What to do
Staging deployment fails Production is untouched. Fix and re-run the failed jobs of that Push run, or follow "Return deployment to calibration-diagnostics" below. A wrong TELEMETRY_GCP_WIF_PROVIDER or TELEMETRY_GCP_DEPLOY_SERVICE_ACCOUNT value fails at the authentication step, before anything changes.
Production deployment fails after "Route traffic to maintenance mode" Data requests get HTTP 503, which Microcosm's delivery service retries. Route traffic back to the step 0 revision: gcloud run services update-traffic "$SERVICE" --project "$PROJECT_ID" --region "$REGION" --to-revisions "PREVIOUS_REVISION=100". That is safe here because this release doesn't change the schema. Then fix and re-run the failed jobs.
Deployed, but the collector misbehaves The same update-traffic command, then delete COLLECTOR_DEPLOY_ENABLED so the next merge doesn't redeploy.
Return deployment to calibration-diagnostics 1. gh variable delete COLLECTOR_DEPLOY_ENABLED --repo PolicyEngine/microcosm-collector. 2. Wait until collector_push_runs_finished (step 3) prints true. 3. gh workflow run telemetry-service.yml -R PolicyEngine/calibration-diagnostics --ref main -f collector_deploy_disabled=true. The run fails without deploying if it isn't from main, isn't confirmed, or a microcosm-collector Push run is still unfinished. This works until calibration-diagnostics#234 merges, and only while microcosm-collector has added no Alembic migration: the older code can't upgrade a database it doesn't know, and its deployment would stop in maintenance mode.

Don't revert this PR to roll back: the revert restores the push trigger and, because it changes the workflow file, starts a deployment from calibration-diagnostics as soon as it merges.

Deployment configuration, checked end to end

I listed variable and secret names with gh api (values not printed), compared variable values between the repos for equality in memory, and read the live Google Cloud resources (read-only) as the deploy service account would see them. I also ran microcosm-collector's scripts/deploy.sh commands require-configuration, validate-environment-target and check-secrets-exist locally with each environment's real settings; all exit 0. The only gcloud call those commands make is the Secret Manager metadata read.

Name Provider Wiring Verified
COLLECTOR_DEPLOY_ENABLED microcosm-collector repo variable job-level if: of deploy-staging and deploy-production in push.yaml Unset (the repo has no variables; rechecked 2026-10-20 at 16:48 UTC). Step 4 sets it. That it enables the jobs is unverified until then.
GH_TOKEN ← github.token GitHub Actions' automatic token for the dispatch run (workflow permissions: contents: read) step env: of "Refuse while a microcosm-collector Push run is queued or in progress" → gh api in require-collector-push-runs-finished.sh microcosm-collector is public, and its Push runs listing answers an unauthenticated request (checked with curl). GitHub's REST reference for this endpoint says it works with GitHub App installation access tokens, which GITHUB_TOKEN is, and "can be used without authentication or the aforementioned permissions if only public resources are requested." No dispatch has exercised it yet; if the request fails, the job fails without deploying.
TELEMETRY_GCP_PROJECT_ID → PROJECT_ID GitHub environment variable (staging, production) job env: → every gcloud call, image path, backup check Present in both; equal to calibration-diagnostics' staging / Production values; the Cloud SQL connection's project matches it
TELEMETRY_GCP_REGION → REGION environment variable --region, Artifact Registry host Present, equal; all resources found in it
TELEMETRY_ARTIFACT_REGISTRY_NAME, TELEMETRY_IMAGE_NAME environment variables image path in build-and-push-image Present, equal; the registry exists; the serving image path matches
TELEMETRY_CLOUD_SQL_CONNECTION environment variable --add-cloudsql-instances (service), --set-cloudsql-instances (job), backup instance Present, equal; matches the live service's and job's annotations; the instance exists
TELEMETRY_RUNTIME_SERVICE_ACCOUNT environment variable gcloud run deploy --service-account Matches the live service
TELEMETRY_MIGRATION_SERVICE_ACCOUNT environment variable gcloud run jobs deploy --service-account Matches the live job
TELEMETRY_SERVICE, TELEMETRY_MIGRATION_JOB environment variables deploy, describe, traffic and job commands Live service and job exist in both environments; staging and production services are distinct
TELEMETRY_DATABASE_SECRET_NAME, TELEMETRY_JWT_SECRET_NAME, TELEMETRY_READ_SECRET_NAME environment variables naming Google Secret Manager secrets --set-secrets → runtime DATABASE_URL, TELEMETRY_JWT_SECRET, TELEMETRY_READ_TOKEN All exist; the live service references exactly these names at latest
TELEMETRY_MIGRATION_DATABASE_SECRET_NAME environment variable naming a Secret Manager secret job --set-secrets DATABASE_URL Exists; the live job references it
TELEMETRY_PRODUCTION_CLOUD_SQL_CONNECTION, TELEMETRY_PRODUCTION_SERVICE environment variables validate-environment-target Passes for both environments
TELEMETRY_GCP_WIF_PROVIDER GitHub environment secret (staging, production) google-github-actions/auth workload_identity_provider Exists in both (set 2026-10-15). Unverified: a secret's value can't be read, so whether it names the microcosm-collector provider is known only when the first staging deployment authenticates
TELEMETRY_GCP_DEPLOY_SERVICE_ACCOUNT GitHub environment secret auth service_account Exists in both. Unverified value, as above
Provider github/microcosm-collector Google Cloud Workload Identity accepts assertion.repository=='PolicyEngine/microcosm-collector' && assertion.ref=='refs/heads/main'; no custom audiences, so the action's default audience matches Verified with gcloud. The deploy service account grants roles/iam.workloadIdentityUser to principalSet://…/attribute.repository/PolicyEngine/microcosm-collector
APP_ID, APP_PRIVATE_KEY PolicyEngine organization secrets visible to microcosm-collector versioning job (release commit) Present; the 0.2.0 release used them
TELEMETRY_MAINTENANCE_MODE --set-env-vars in deploy.sh runtime The live service sets only this plain variable
TELEMETRY_HF_ORG, TELEMETRY_JWT_ISSUER, TELEMETRY_JWT_AUDIENCE, TELEMETRY_SESSION_TOKEN_TTL_SECONDS not set anywhere; defaults in app.py runtime The live service sets none of them, as today
GITHUB_SHA, GITHUB_OUTPUT, GITHUB_RUN_ID, GITHUB_RUN_ATTEMPT, step outputs IMAGE_URI, REVISION GitHub Actions image tag, job outputs, backup description Same wiring as this repo's workflow, which deployed production on 2026-10-09

The collector's workflow, scripts, image and settings match what telemetry-service.yml deploys today: the same 15 variables with equal values, the same Cloud Run flags and Secret Manager names, the same staging-then-production sequence, and the same runtime code: with microcosm-collector#11 merged into today's main, migrations/, alembic.ini and Dockerfile have the same git object IDs as telemetry-service/ at #219's head, and under telemetry_collector/ only the new, unused orrery/ subpackage from microcosm-collector#9 differs. Differences: the actions are pinned by commit, the deploy runs on microcosm-collector's release commit rather than on the merge, and microcosm-collector's environments accept only main.

This PR adds no variable or secret. It adds only the dispatch input collector_deploy_disabled, and reads GitHub's automatic github.token.

Tests

Commit Red (before the change) Green (after)
0e7a279 Stop deploying the collector from calibration-diagnostics on push frontend/tests/test_telemetry_service_workflow.py: 4 failed, 1 passed (the passing test requires that pull requests are still validated, which the change must keep) 5 passed; telemetry-service/tests/test_deployment_workflow.py (updated): 8 passed; actionlint 1.7.12 on the workflow: no findings
fc660c4 Refuse a redeploy from another branch or during a collector Push run frontend/tests/test_telemetry_service_workflow.py (rewritten): 9 failed, 3 passed. The failing tests require that every dispatch runs the refusal job and deploy-staging needs it; that a dispatch that isn't from main or isn't confirmed fails; and, with a gh stub that records its arguments and returns a fixture, that the run check fails for a queued, in_progress or waiting Push run, fails when the API request fails, passes when every run is completed, and uses the workflow token. The 3 passing tests check behaviour the change keeps. 12 passed; telemetry-service/tests/test_deployment_workflow.py (unchanged; it requires no multi-line run: blocks, which is why the check is a script): 8 passed; actionlint 1.7.12 on the workflow and shellcheck 0.11.0 on the new script: no findings

After each commit, both files passed again (5 and 8, then 12 and 8). Ruff lint and format pass on the test files. Locally I ran only these two test files. CI runs the rest, including the telemetry-service validation that this PR triggers.

Re-review fixes (2026-10-20)

The re-review of e0a7fb0 (now fc660c4) found one bug, in the runbook rather than the code: step 3 didn't say that merging this PR is a production dashboard deploy that has to wait for other main CI runs and deploys. The fix is a description-only change, so there is no fix commit, no failing-test step and no new test count. The fix left the branch at e0a7fb0 (now fc660c4; see the rebase below), so calibration-diagnostics#234 needs nothing carried over.

Change Where Red Green
Merge this PR only when no calibration-diagnostics main CI or "Deploy production" run is unfinished, and confirm its own deploy succeeds Runbook step 3 n/a (description only) n/a
If steps 4–6 come later, merge nothing to microcosm-collector's main in between, or else repeat step 3's wait and "Update branch" Runbook step 3 n/a n/a
Record steps 1 and 2 as done; correct 0.2.1 to 0.3.1; update the serving revisions, the checked main head and the list of open collector PRs Runbook steps 0–3 n/a n/a
The workflow token's read of microcosm-collector's runs is now settled from GitHub's REST reference; the deploy account's roles were re-read with gcloud Configuration table, manual steps n/a n/a

Tests re-run on e0a7fb0 (now fc660c4) and on a trial merge with main at eb3da81 (aborted afterwards): frontend/tests/test_telemetry_service_workflow.py 12 passed and telemetry-service/tests/test_deployment_workflow.py 8 passed on both trees. Ruff, actionlint and shellcheck reported no findings.

Rebase onto main (2026-10-20)

This branch and calibration-diagnostics#234 were rebased onto main at 19be8dc on 2026-10-20, with no change to their own content. b195940 is now 0e7a279 and e0a7fb0 is now fc660c4. The rebase had no conflicts: git range-diff shows both commits unchanged, and the diff against main is identical to the previous diff against 5cc4723, including the workflow's variable and secret names. On fc660c4, frontend/tests/test_telemetry_service_workflow.py (12 passed) and telemetry-service/tests/test_deployment_workflow.py (8 passed) pass, and Ruff, actionlint and shellcheck report no findings.

Manual steps (not done by this PR)

  • Step 4 of the runbook: set COLLECTOR_DEPLOY_ENABLED=true as a microcosm-collector repo variable.
  • After calibration-diagnostics#234, delete this repo's staging environment, which only telemetry-service.yml uses. It still holds the secrets MICROCOSM_TELEMETRY_COLLECTOR_READ_TOKEN and TELEMETRY_HF_QUALIFICATION_TOKEN (names listed 2026-10-20). The plan records both as deleted under its stage 0.1 (2026-10-19), but that deletion removed other copies, not these; they go with the environment. Stage 0.1 also said to rotate the read token if this copy is current, which can't be checked from GitHub because it doesn't return secret values.
  • After calibration-diagnostics#234, in Production, delete the TELEMETRY_* variables other than TELEMETRY_GCP_PROJECT_ID and TELEMETRY_READ_SECRET_NAME. Keep the Production secrets TELEMETRY_GCP_WIF_PROVIDER and TELEMETRY_GCP_DEPLOY_SERVICE_ACCOUNT, and the calibration-diagnostics Workload Identity provider and its binding (until the follow-up below): the dashboard deploy still uses them for its read-secret check.
  • Follow-up after calibration-diagnostics#234: give the dashboard deploy its own service account. deploy.yml authenticates as the service account in TELEMETRY_GCP_DEPLOY_SERVICE_ACCOUNT, which is the collector's deploy service account, only to run gcloud secrets describe on the read secret. That account has project-level roles/run.admin, the custom role telemetryDatabaseBackupCreator and roles/serviceusage.serviceUsageConsumer (re-read with gcloud on 2026-10-20). Create a service account with only roles/secretmanager.viewer on the read secret, let the github/calibration-diagnostics provider's principalSet://…/attribute.repository/PolicyEngine/calibration-diagnostics use it (roles/iam.workloadIdentityUser), and set the Production secret TELEMETRY_GCP_DEPLOY_SERVICE_ACCOUNT to it. Once a dashboard deploy passes, remove calibration-diagnostics' roles/iam.workloadIdentityUser binding from the collector's deploy service account. Until Remove telemetry-service/ now that the collector deploys from its own repo #234 merges, the rollback dispatch here still needs that binding.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
calibration-diagnostics Ready Ready Preview Oct 20, 2026 6:21pm UTC

Request Review

anth-volk and others added 2 commits October 20, 2026 22:17
PolicyEngine/microcosm-collector releases and deploys the collector from its
own repo. telemetry-service.yml no longer runs on push, so merging a change to
telemetry-service/ deploys nothing. Pull requests that change the service are
still validated.

The workflow keeps one manual redeploy of main, for rolling the deploy-source
switch back. Its deployment jobs run only when the person dispatching it
checks collector_deploy_disabled, confirming that COLLECTOR_DEPLOY_ENABLED is
unset in microcosm-collector and no deployment is running there, because both
repos deploy the same Cloud Run services. An unconfirmed dispatch fails in a
job of its own instead of skipping the deployment jobs silently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The manual redeploy's refusal job ran only for an unconfirmed dispatch, so a
dispatch from a branch other than main with collector_deploy_disabled checked
finished green with both deployment jobs skipped.

The job now runs on every dispatch, and deploy-staging needs it. It fails
unless the dispatch is from main with the confirmation checked, and then fails
while any PolicyEngine/microcosm-collector Push run is queued, waiting or in
progress. That repo is public, so the workflow token lists its runs through
the REST API. A release run reads COLLECTOR_DEPLOY_ENABLED only when it
reaches its deployment jobs, so an unfinished run may still deploy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk force-pushed the collector-deploy-switch branch from e0a7fb0 to fc660c4 Compare October 20, 2026 18:20
@anth-volk
anth-volk merged commit a45aaf1 into main Oct 20, 2026
9 checks passed
@anth-volk
anth-volk deleted the collector-deploy-switch branch October 20, 2026 18:42

This branch was successfully deployed

1 active deployment
Preview — fc660c4e Deployed Oct 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stop deploying the collector from calibration-diagnostics once microcosm-collector deploys it

1 participant