Firebase-Main Holding URL
firebase-main branch and nest-firebase.thephenom.app form a frozen snapshot of the currently-deployed N.E.S.T. production. They exist so that forward migration work on main always has a working fallback to point to.TL;DR. nest-firebase.thephenom.app serves whatever was on phenom-backend:main at commit b46e3d1 (the “Firebase-era” cohort label — the codebase had already removed direct Firebase calls in 45689f2, but the deploy continued to be referred to as the Firebase-era snapshot). The branch is frozen at the application-code layer and auto-deploys on push.
Why this exists
Phenom is mid-migration off the Firebase-era N.E.S.T. architecture. Every forward step on main (auth changes, Worker rewrites, dashboard polish) is one step further from the version that’s currently working in production. Without a parallel deploy, if main ever ships something broken we have nothing to fall back to other than a git revert and a redeploy.
firebase-main is the fallback. nest-firebase.thephenom.app is the URL pointing at it.
Topology
graph LR
Branch(["github.com/Phenom-earth/phenom-backend<br/>branch: firebase-main"])
Pages(["CF Pages<br/>phenom-backend-firebase"])
Worker(["CF Worker<br/>nest-api-firebase"])
Domain(["nest-firebase.thephenom.app"])
Access(["CF Access<br/>nest-firebase app"])
Cognito(["AWS Cognito<br/>shared with prod"])
Hasura(["Hasura GraphQL<br/>chat-testing.thephenom.app"])
Branch -->|"push triggers GH Actions"| Pages
Branch -->|"push triggers GH Actions"| Worker
Domain -->|"DNS proxied CNAME"| Pages
Domain -->|"routes /api/* + /health"| Worker
Access -->|"gates"| Domain
Worker -->|"Bearer JWT"| Hasura
Worker -.->|"verifies JWT"| Cognito| Surface | Resource | Same-as-prod? |
|---|---|---|
| Branch | firebase-main on Phenom-earth/phenom-backend | Application code yes; .github/workflows/nest-ci.yml has one extra commit adding the firebase-main deploy jobs. |
| Pages project | phenom-backend-firebase (production_branch=firebase-main) | Independent project; isolated deploy history. |
| Pages subdomain | phenom-backend-firebase.pages.dev | Independent. |
| Worker script | nest-api-firebase | Built from the same admin_sandbox/nest-api/ source as prod’s nest-api. Isolated script (wrangler deploy --name nest-api-firebase). |
| Worker routes | nest-firebase.thephenom.app/api/*, nest-firebase.thephenom.app/health → nest-api-firebase | Isolated. |
| Custom domain | nest-firebase.thephenom.app | Proxied CNAME → phenom-backend-firebase.pages.dev. |
| CF Access app | nest-firebase — gated by the GitHub-IdP INT policy. (nest, nest-prod, dev-nest CF Access apps were all deleted on 2026-05-16; only nest-firebase keeps the gate now because the snapshot is intentionally team-only.) | |
| Cognito | us-east-1_AkG9mnbjA user pool, client 2eq1vf0nvl5o3rha2vshm8j0mn | Same as prod. |
| Hasura | chat-testing.thephenom.app/v1/graphql | Same as prod (per layer-4 workflow env). |
How deploys work
Pushing to firebase-main triggers two jobs in .github/workflows/nest-ci.yml:
- Layer 5a — Deploy nest-api-firebase Worker — runs
npx wrangler deploy --name nest-api-firebasefromadmin_sandbox/nest-api/. The--nameoverride is the load-bearing piece: it deploys the same code asnest-apibut as a distinct Worker script, so future Worker work onmaincannot accidentally overwrite the holding URL’s Worker. - Layer 5b — Deploy to nest-firebase.thephenom.app — builds
admin_sandbox/phenom/with the same env vars as Layer 4b (VITE_HASURA_GRAPHQL_URL=https://chat-testing.thephenom.app/v1/graphql,VITE_COGNITO_CLIENT_ID=2eq1vf0nvl5o3rha2vshm8j0mn, etc.) and runsnpx wrangler pages deploy dist --project-name=phenom-backend-firebase --branch=firebase-main.
Both jobs are gated on github.ref == 'refs/heads/firebase-main' — they don’t fire on any other branch.
Posting commits to firebase-main
Default policy: don’t. firebase-main is a snapshot lane. The application-code tree under admin_sandbox/ should remain byte-identical to whatever was on main at b46e3d1.
The narrow exceptions:
- Critical hotfix that has to ship to the holding URL — cherry-pick from
main, prefer a CI-only fix to a code change. - CI workflow changes specifically for the firebase-main deploy jobs — those have to live on this branch by GitHub’s “workflow file at the branch HEAD” rule.
Anything else: open the change against main (or its feature branch), let it land there, and decide separately whether the holding URL should track that change. The whole point of the snapshot is that it does NOT automatically inherit new application code.
S3 bucket + IAM principal
nest-api-firebase signs presigned GetObject URLs against the dedicated phenom-firebase-media S3 bucket. The bucket is a frozen mirror of phenom-dev-media-staging as of 2026-05-14; no further writes happen against it. The Worker reads the bucket name from env.S3_BUCKET (set in wrangler.toml [env.firebase.vars]).
Both the bucket and the IAM principal that signs the URLs are managed in phenom-infra/environments/development/nest-api-media.tf:
| Terraform resource | AWS name |
|---|---|
aws_s3_bucket.firebase_media | phenom-firebase-media |
aws_iam_user.nest_api_firebase_s3 | phenom-nest-api-firebase-s3 |
aws_iam_user_policy.nest_api_firebase_s3 (inline) | GetMediaObjects — s3:GetObject + s3:ListBucket scoped to phenom-firebase-media only |
Access keys are intentionally not in Terraform state — they live in the Cloudflare Worker secrets AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY on the nest-api-firebase script and are rotated via wrangler secret put. This mirrors the App Runner credential pattern already established in phenom-infra.
Worker secrets
The nest-api-firebase Worker is deployed with the same code shape as nest-api but without secrets at first deploy — Cloudflare doesn’t let secrets be cloned because they’re write-only at the API surface. The four secrets it needs:
cd ~/c/phenom-backend/admin_sandbox/nest-api
bunx wrangler secret put AWS_ACCESS_KEY_ID --name nest-api-firebase
bunx wrangler secret put AWS_SECRET_ACCESS_KEY --name nest-api-firebase
bunx wrangler secret put CF_ACCESS_DOCS_CLIENT_ID --name nest-api-firebase
bunx wrangler secret put CF_ACCESS_DOCS_CLIENT_SECRET --name nest-api-firebase
For the AWS pair, source the access key created from aws_iam_user.nest_api_firebase_s3 (the IAM user managed in phenom-infra above). For the CF Access pair, source the service token named nest-firebase docs proxy in the Pavestar Cloudflare Access dashboard (already linked to the int-docs Access policy).
Confirm post-set:
bunx wrangler secret list --name nest-api-firebase
CF Access
The nest-firebase Access app shares the same allow-list as the prod nest app via reusable Cloudflare Access policy UIDs (INT and NEST & DEV NEST Service access). Changes to who can reach prod NEST automatically apply to the holding URL — no second policy to maintain.
If you intentionally want the holding URL accessible to a wider/narrower audience than prod, create a new app-specific policy on the nest-firebase app instead of editing the shared INT policy.
Decommissioning
When the holding URL is no longer needed (post-migration confidence restored), tear down in this order to avoid orphan resources:
- Disable the CI jobs by removing the
firebase-maintriggers in.github/workflows/nest-ci.ymlonfirebase-main(or simply delete the branch). - Delete the Worker routes for
nest-firebase.thephenom.app/api/*and/healthon thethephenom.appzone. - Delete the Worker script
nest-api-firebase. - Detach the custom domain
nest-firebase.thephenom.appfrom thephenom-backend-firebasePages project. - Delete the DNS CNAME record for
nest-firebasein thethephenom.appzone. - Delete the Pages project
phenom-backend-firebase. - Delete the CF Access app
nest-firebase. - Delete the branch
firebase-mainonPhenom-earth/phenom-backend(last — keep the git history available longest).
All eight steps are reversible from CF dashboard if you change your mind mid-teardown.
Related
- Infrastructure & Service Map — the equivalent map for current production.
- Admin Dashboard — N.E.S.T. UI documentation.
~/.claude/PAI/MEMORY/WORK/20260513-nest-firebase-holding-branch/ISA.md— the ISA that drove the original stand-up, including 37 verification criteria and their evidence.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.