These rules apply to GitHub Actions workflows and custom actions under .github/.
- Every workflow that mutates a persistent Cloud Run service/job, GKE Helm release, or traffic/promotion state must use a workflow-level concurrency group scoped to the exact target and logical environment. Manual and automatic entry points for the same target must resolve to the same group.
- Deployment group names are a cross-workflow API. Keep them aligned with
.github/scripts/check-deployment-concurrency.py; usecancel-in-progress: falseso a newer run cannot interrupt a remote mutation or a staged validation/traffic promotion. deploy-backend-stack-<environment>intentionally covers the four backend Cloud Run services, traffic repair, backend-listen, LLM gateway, and backend-secrets. Those paths share mutable releases, while unrelated services retain their own groups and may deploy in parallel.- GitHub concurrency is serialization, not a FIFO queue: only one pending run is retained and ordering is not guaranteed. Deploy workflows must not assume that every intermediate commit will run.
- Use immutable image tags for deploys. Build and push the short SHA tag, then deploy that exact tag.
- Development desktop-backend acceptance must stage
GCP_SERVICE_ACCOUNTonly in a mode-0600 runner file for Firebase probe signing, validate that its project matchesFIREBASE_AUTH_PROJECT_ID, and delete it after the probe; never copy it into the image or deploy it as runtime configuration. - Do not deploy Cloud Run services from an untagged image path; use
image:...:${SHORT_SHA}so revisions show the source commit. - Do not let Helm chart-only deploys reset GKE workloads to
latest. Preserve the currently deployed immutable tag or require an explicit tag input. - Every GKE Deployment deploy must wait for rollout completion with
kubectl rollout status ... --timeout=...and fail on timeout. - For chart-only backend-listen deploys, fail if the current deployed image tag is missing or
latest; use the backend deploy workflow to establish an immutable tag first. - Keep rollout checks close to the deploy step they verify, especially for
backend-listen,pusher,llm-gateway,parakeet,diarizer, andvad. - Use
backend/scripts/deploy_status_report.pyas a strict gate on success paths; use it with|| trueonly after a primary rollout/traffic command already failed. - Full backend deploys must derive one immutable Cloud Run plus
backend-listenrelease vector and runbackend/scripts/verify_backend_release_vector.pyafter traffic promotion; a mixed or partially applied serving vector must fail the workflow and emit evidence for a retry. - Before restarting GKE workloads that depend on
backend-secrets, wait for ExternalSecret Ready and runbackend/scripts/verify_k8s_secret_keys.py; never print secret values. - Before any pusher Helm mutation, verify
${ENV}-omi-backend-configexists so a missing shared runtime ConfigMap cannot replace the healthy replica. - Backend deploy workflows may only run Firestore index readiness with
--check-onlyagainstRUNTIME_GCP_PROJECT_ID; run it in an isolated job from the approved commit withGCP_FIRESTORE_READONLY_CREDENTIALS, and bind manual deploys to the exact checked candidate SHA. This intentionally read-only credential must be set separately in bothdevelopmentandprodGitHub Environments. A failed gate may upload only a locally revalidated, bounded, redacted schema proposal artifact; Firestore index writes are owned solely bygcp_firestore_indexes.yml, which auto-triggers onpushtomainwhenfirestore.indexes.jsonchanges (targetingprod, gated by that environment's required reviewer) and still requires the typedAPPLY_FIRESTORE_INDEXESconfirmation onworkflow_dispatch. It holds its ownfirestore-schema-${environment}lock, never the backend-stack lock: create-only reconciliation cannot invalidate a readiness answer, and sharing the deploy lock let awaitingdeploy approval block schema repair during the #11684 outage. Reconciliation never deletes, so--check-only/--dry-runreport undeclared live indexes as non-blocking warnings and removal stays a human action. - When editing workflows, keep
actionlintcoverage in CI so YAML and GitHub expression mistakes fail before merge.