Skip to main content

Architecture

The app bundle is pure code it reads all data and models from the GCS CDN (gs://off-the-pace-cdn) at runtime. So a code deploy runs entirely in CI. The data export needs the 6 GB warehouse, which is not in CI producing and publishing data is an operator step that runs before a release. CI then validates that data in staging, promotes it to prod, and deploys the code.

One-time cloud setup

CI authenticates to GCP with Workload Identity Federation no long-lived JSON key.

Provision WIF

Run the provisioning script from a machine with owner-level ADC:
It prints two values. Store them as repository Actions variables (Settings → Secrets and variables → Actions → Variables tab they are not sensitive):Until GCP_WORKLOAD_IDENTITY_PROVIDER is set, deploy.yml and preview.yml skip on their if: guards releases won’t show a red deploy before setup.

Enable GCS object versioning

Object versioning makes data rollback restore real parquet bytes rather than just the version pointer:
This is also codified in infra/terraform/bucket.tf (make tf-apply).

Cutting a release

Versioning is automated by release-please (release-please.yml), driven by Conventional Commits on main.

Land conventional commits on main

Use feat:, fix:, perf:, etc. as commit prefixes. release-please keeps an open “chore: release X.Y.Z” PR with the computed version and a generated CHANGELOG.md. Review it.

Publish data to staging

From a machine with the 6 GB warehouse:

Merge the release PR

That tags the release and publishes a GitHub Release, which triggers deploy.yml:
  1. Smoke staging
  2. Build the app bundle
  3. Promote staging → prod (atomic manifest flip)
  4. firebase deploy (code)
  5. Smoke prod
make app-deploy still works for emergencies but bypasses staging/smoke prefer the pipeline.

Preview deploys

Every app PR (preview.yml) builds against the staging data prefix and deploys to a Firebase preview channel pr-<n>, posting the shareable URL as a PR comment (expires 7 days). Fork PRs are skipped no WIF access.

Rollback

Prod’s data version pointer is the version field in _manifest.json. Every promotion archives the superseded prod manifest to gs://…/data/manifest-archive/.
Parquet bytes vs. version pointer. make app-rollback restores what the app loads, not necessarily what the bucket stores (publish overwrites parquet in place). To recover the actual parquet bytes you need object versioning enabled (step 2 of setup):

Workflows at a glance

Local equivalents

Data pipeline

The DAG that produces the data that deployment promotes.

Observability

Synthetic monitors that guard the live serving plane after every deploy.