Repository navigation
Stop deploying the collector from calibration-diagnostics on push - #233
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
anth-volk
added this pull request to stack #235
October 19, 2026 20:35
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
force-pushed
the
collector-deploy-switch
branch
from
October 20, 2026 18:20
e0a7fb0 to
fc660c4
Compare
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #231
Summary
PolicyEngine/microcosm-collector releases and deploys the collector from its own repo once its repo variable
COLLECTOR_DEPLOY_ENABLEDistrue. 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: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.telemetry-service/or the workflow (tests, migration check, Ruff, image build), for example calibration-diagnostics#219.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", anddeploy-stagingneeds it. That job fails, so a refused dispatch is a failed run rather than a green run with both deployment jobs skipped, when:main, or the person dispatching didn't checkcollector_deploy_disabled(confirming thatCOLLECTOR_DEPLOY_ENABLEDis unset in microcosm-collector); ortelemetry-service/scripts/require-collector-push-runs-finished.shlists 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.mdnow 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-apiGoogle 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 readCOLLECTOR_DEPLOY_ENABLEDonly when the run reaches them, and runs can wait a long time for runners: the run for microcosm-collector#9's release commiteac60d4was 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'smainas of that release commit, without #11, to staging and then production.0. Before starting
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.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
mainis a production deployment: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).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.Record the production service's current state (read-only; repeat with
stagingfor the staging service):Keep the output.
servingis the revision to roll back to. On 2026-10-20 at 16:45 UTC, production serves revisionmicrocosm-telemetry-00024-reqand staging servesmicrocosm-telemetry-staging-00023-pig. Both run the image built from calibration-diagnosticsa213d48(#219's merge commit).Steps
Done. Path A only: merge calibration-diagnostics#219 with "Create a merge commit".
telemetry-service.ymldeploys it to staging, then production. Wait for both jobs to succeed.Done. Merge microcosm-collector#4, then check what the first deployment from microcosm-collector will change.
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 commitde126c7) 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.3dfbe05, microcosm-collector#11 must be updated first.main(path A: calibration-diagnosticsmain; path B: Apply each telemetry event batch incrementally instead of re-materializing the run #219's head instead):Expected today: only the files under
telemetry_collector/orrery/(from microcosm-collector#9),pyproject.tomlanduv.lock(the package's own name, version and license; no dependency changes), and nothing undermigrations/. Review anything else before going on, or hold the PR that added it. The image copies onlypyproject.toml,uv.lock,alembic.ini,migrationsandtelemetry_collector;.dockerignorediffers only by excluding more non-runtime files.Wait until no microcosm-collector Push run is queued or in progress, then merge this PR. In bash or zsh:
It prints
falseevery minute, thentrueonce no Push run is queued, waiting or in progress and the run formain'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 printstrueformainatde126c7.From here until step 6 is done, merge nothing else to microcosm-collector's
main.Then:
mainis 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 unfinishedmainCI run when a newer commit reachesmain(cancel-in-progressonci-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.ymldoesn't run:gh run list -R PolicyEngine/calibration-diagnostics --workflow telemetry-service.yml --limit 3shows no run for the merge commit.mainafter 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.mainin 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.Run
collector_push_runs_finishedonce more. Only if it printstrue, setCOLLECTOR_DEPLOY_ENABLEDtotrueas a repository variable of microcosm-collector, not in itsstagingorproductionenvironment (Settings → Secrets and variables → Actions → Variables → Repository variables, orgh variable set COLLECTOR_DEPLOY_ENABLED --repo PolicyEngine/microcosm-collector --body true). The deployment jobs read it in their job-levelif:, 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 exactlytrue.Straight away, merge microcosm-collector#11 with "Create a merge commit". If GitHub says its branch is out of date, something merged to
mainafter step 3: deleteCOLLECTOR_DEPLOY_ENABLEDand 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:
release_commitis empty until the merge commit's run has pushed the release; repeat until it prints one SHA. Checked against microcosm-collector#9: merge commit5cf1898gives release commiteac60d4and run 39326286725. Wait for that run's two deployment jobs to succeed.Verify (next section), for staging and production.
After a few normal builds, mark calibration-diagnostics#234 ready, change its base to
mainby 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
mainthat changestelemetry-service/main, while no microcosm-collector Push run is unfinishedmain, for rollback, after unsetting the variable and while no microcosm-collector Push run is unfinishedmainmainAt 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 describecommand from step 0 and compare:urlis 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.servingis a new revision, andimageends in:$release_commit, the SHA from step 5. The image tag is theGITHUB_SHAof the run that built it. A tag whose commit message is "Update microcosm-collector" proves nothing more: every release commit has that message.cloud_sqlequals the step 0 value and$CLOUD_SQL_CONNECTION;secretslists the same three Secret Manager names.curl -fsS "URL/ready"succeeds./readyneeds a database connection at the current Alembic revision.gcloud run jobs executions list --job "$MIGRATION_JOB" --project "$PROJECT_ID" --region "$REGION" --limit 1shows a successful execution.gcloud sql backups list --instance "${CLOUD_SQL_CONNECTION##*:}" --project "$PROJECT_ID" --limit 1showstelemetry-release-RUN_ID-ATTEMPT, whereRUN_IDis thedatabaseIdthat step 5'sgh run list --commit "$release_commit"printed./microcosm/staging?view=progress) still lists runs registered before the switch, and the next Microcosm build's events appear there.Roll back
TELEMETRY_GCP_WIF_PROVIDERorTELEMETRY_GCP_DEPLOY_SERVICE_ACCOUNTvalue fails at the authentication step, before anything changes.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.update-trafficcommand, then deleteCOLLECTOR_DEPLOY_ENABLEDso the next merge doesn't redeploy.gh variable delete COLLECTOR_DEPLOY_ENABLED --repo PolicyEngine/microcosm-collector. 2. Wait untilcollector_push_runs_finished(step 3) printstrue. 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 frommain, 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'sscripts/deploy.shcommandsrequire-configuration,validate-environment-targetandcheck-secrets-existlocally with each environment's real settings; all exit 0. The onlygcloudcall those commands make is the Secret Manager metadata read.COLLECTOR_DEPLOY_ENABLEDif:ofdeploy-staginganddeploy-productioninpush.yamlGH_TOKEN←github.tokenpermissions: contents: read)env:of "Refuse while a microcosm-collector Push run is queued or in progress" →gh apiinrequire-collector-push-runs-finished.shcurl). GitHub's REST reference for this endpoint says it works with GitHub App installation access tokens, whichGITHUB_TOKENis, 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_IDstaging,production)env:→ everygcloudcall, image path, backup checkstaging/Productionvalues; the Cloud SQL connection's project matches itTELEMETRY_GCP_REGION→REGION--region, Artifact Registry hostTELEMETRY_ARTIFACT_REGISTRY_NAME,TELEMETRY_IMAGE_NAMEbuild-and-push-imageTELEMETRY_CLOUD_SQL_CONNECTION--add-cloudsql-instances(service),--set-cloudsql-instances(job), backup instanceTELEMETRY_RUNTIME_SERVICE_ACCOUNTgcloud run deploy --service-accountTELEMETRY_MIGRATION_SERVICE_ACCOUNTgcloud run jobs deploy --service-accountTELEMETRY_SERVICE,TELEMETRY_MIGRATION_JOBTELEMETRY_DATABASE_SECRET_NAME,TELEMETRY_JWT_SECRET_NAME,TELEMETRY_READ_SECRET_NAME--set-secrets→ runtimeDATABASE_URL,TELEMETRY_JWT_SECRET,TELEMETRY_READ_TOKENlatestTELEMETRY_MIGRATION_DATABASE_SECRET_NAME--set-secrets DATABASE_URLTELEMETRY_PRODUCTION_CLOUD_SQL_CONNECTION,TELEMETRY_PRODUCTION_SERVICEvalidate-environment-targetTELEMETRY_GCP_WIF_PROVIDERstaging,production)google-github-actions/authworkload_identity_providermicrocosm-collectorprovider is known only when the first staging deployment authenticatesTELEMETRY_GCP_DEPLOY_SERVICE_ACCOUNTauthservice_accountgithub/microcosm-collectorassertion.repository=='PolicyEngine/microcosm-collector' && assertion.ref=='refs/heads/main'; no custom audiences, so the action's default audience matchesgcloud. The deploy service account grantsroles/iam.workloadIdentityUsertoprincipalSet://…/attribute.repository/PolicyEngine/microcosm-collectorAPP_ID,APP_PRIVATE_KEYversioningjob (release commit)TELEMETRY_MAINTENANCE_MODE--set-env-varsindeploy.shTELEMETRY_HF_ORG,TELEMETRY_JWT_ISSUER,TELEMETRY_JWT_AUDIENCE,TELEMETRY_SESSION_TOKEN_TTL_SECONDSapp.pyGITHUB_SHA,GITHUB_OUTPUT,GITHUB_RUN_ID,GITHUB_RUN_ATTEMPT, step outputsIMAGE_URI,REVISIONThe collector's workflow, scripts, image and settings match what
telemetry-service.ymldeploys 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'smain,migrations/,alembic.iniandDockerfilehave the same git object IDs astelemetry-service/at #219's head, and undertelemetry_collector/only the new, unusedorrery/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 onlymain.This PR adds no variable or secret. It adds only the dispatch input
collector_deploy_disabled, and reads GitHub's automaticgithub.token.Tests
0e7a279Stop deploying the collector from calibration-diagnostics on pushfrontend/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)telemetry-service/tests/test_deployment_workflow.py(updated): 8 passed; actionlint 1.7.12 on the workflow: no findingsfc660c4Refuse a redeploy from another branch or during a collector Push runfrontend/tests/test_telemetry_service_workflow.py(rewritten): 9 failed, 3 passed. The failing tests require that every dispatch runs the refusal job anddeploy-stagingneeds it; that a dispatch that isn't frommainor isn't confirmed fails; and, with aghstub that records its arguments and returns a fixture, that the run check fails for aqueued,in_progressorwaitingPush run, fails when the API request fails, passes when every run iscompleted, and uses the workflow token. The 3 passing tests check behaviour the change keeps.telemetry-service/tests/test_deployment_workflow.py(unchanged; it requires no multi-linerun: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 findingsAfter 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-servicevalidation that this PR triggers.Re-review fixes (2026-10-20)
The re-review of
e0a7fb0(nowfc660c4) 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 othermainCI 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 ate0a7fb0(nowfc660c4; see the rebase below), so calibration-diagnostics#234 needs nothing carried over.mainCI or "Deploy production" run is unfinished, and confirm its own deploy succeedsmainin between, or else repeat step 3's wait and "Update branch"mainhead and the list of open collector PRsgcloudTests re-run on
e0a7fb0(nowfc660c4) and on a trial merge withmainateb3da81(aborted afterwards):frontend/tests/test_telemetry_service_workflow.py12 passed andtelemetry-service/tests/test_deployment_workflow.py8 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
mainat19be8dcon 2026-10-20, with no change to their own content.b195940is now0e7a279ande0a7fb0is nowfc660c4. The rebase had no conflicts:git range-diffshows both commits unchanged, and the diff againstmainis identical to the previous diff against5cc4723, including the workflow's variable and secret names. Onfc660c4,frontend/tests/test_telemetry_service_workflow.py(12 passed) andtelemetry-service/tests/test_deployment_workflow.py(8 passed) pass, and Ruff, actionlint and shellcheck report no findings.Manual steps (not done by this PR)
COLLECTOR_DEPLOY_ENABLED=trueas a microcosm-collector repo variable.stagingenvironment, which onlytelemetry-service.ymluses. It still holds the secretsMICROCOSM_TELEMETRY_COLLECTOR_READ_TOKENandTELEMETRY_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.Production, delete theTELEMETRY_*variables other thanTELEMETRY_GCP_PROJECT_IDandTELEMETRY_READ_SECRET_NAME. Keep the Production secretsTELEMETRY_GCP_WIF_PROVIDERandTELEMETRY_GCP_DEPLOY_SERVICE_ACCOUNT, and thecalibration-diagnosticsWorkload Identity provider and its binding (until the follow-up below): the dashboard deploy still uses them for its read-secret check.deploy.ymlauthenticates as the service account inTELEMETRY_GCP_DEPLOY_SERVICE_ACCOUNT, which is the collector's deploy service account, only to rungcloud secrets describeon the read secret. That account has project-levelroles/run.admin, the custom roletelemetryDatabaseBackupCreatorandroles/serviceusage.serviceUsageConsumer(re-read withgcloudon 2026-10-20). Create a service account with onlyroles/secretmanager.vieweron the read secret, let thegithub/calibration-diagnosticsprovider'sprincipalSet://…/attribute.repository/PolicyEngine/calibration-diagnosticsuse it (roles/iam.workloadIdentityUser), and set theProductionsecretTELEMETRY_GCP_DEPLOY_SERVICE_ACCOUNTto it. Once a dashboard deploy passes, remove calibration-diagnostics'roles/iam.workloadIdentityUserbinding 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