Skip to content

Cloud PR preview

Deploy a branch's ingestion/job code as an isolated cloud preview so it can be tested from the cloud before merge — without touching the shared test environment's data.

A preview is a throwaway Cloud Run Job (<job>-<id>, labeled preview-id=<id>), pointed at:

  • its own Supabase preview branch (isolated Postgres),
  • a per-preview Mongo database prefix and Neo4j tenantId namespace,

all derived from one id. Nothing shared is mutated — no shared -test service is redeployed, so its traffic and latest-revision template are never touched. The job bills only while it runs.

One script (scripts/preview/preview.py) backs the local just recipes; a preview is deployed and torn down on demand, never automatically from CI.

Safe to run autonomously

Agents may deploy and tear down previews without asking — the tool is safe by construction, so a preview is reversible work, not an irreversible deploy:

  • Never touches production, never mutates a shared service. A component can only resolve to an allowlisted job (JOBS in preview.py); the preview runs as its own <job>-<id> resource. An arbitrary or -production name is not a valid component.
  • IX_ENVIRONMENT=test. Enum-keyed logic (including the production-write guard) stays on test; writes land in the PR's Supabase branch and <id> Mongo/Neo4j namespaces, never production.
  • Reversible + self-cleaning. cloud-preview-down removes the preview jobs and the secret; the Supabase branch is deleted (or reaped) on teardown.

Obligation: tear the preview down when done (just cloud-preview-down) so previews don't accumulate.

From your machine — just cloud-preview

Prerequisites: gcloud application-default credentials (gcloud auth application-default login) and Docker. Run from backend/:

just cloud-preview                         # deploy the document-loader job (default; id = branch name)
just cloud-preview job:document-loader     # same, explicit
just cloud-preview-down                    # tear it down (jobs, secret, Supabase branch)

Run a preview job once (deploy, then execute with per-run env):

just cloud-preview-run document-loader TENANT_ID=testfeatures.com LIMIT=1

The loader's Redis state (incremental timestamp, FAQ sync fingerprint) is keyed with the preview's tenant prefix, so a preview run never moves the test markers.

Jobs are the whole reason this exists: document-loader (knowledge ingestion) cannot run locally — it needs the NAT static IP whitelisted in MongoDB Atlas — so a per-branch Cloud Run Job (document-loader-<id>, labeled preview-id=<id>) is the only way to test an ingestion change before merge.

Use the knowledge testing decision matrix before deploying. cloud-preview ensures the branch's Supabase preview and writes its credentials to the generated local env files, so a local UI/Admin API started afterward can inspect the same workflow rows. The cloud job also receives preview-only Mongo/Neo4j prefixes that local processes do not; do not split one operation across local and preview workers/loaders unless callback URLs, OIDC audiences, queues and every data target have been aligned deliberately.

id defaults to the current branch; override it positionally: just cloud-preview <component> <id>, just cloud-preview-down <id>. Deploy prints PREVIEW_JOB=<name>.

Isolation and cleanup

Store Isolation
Supabase dedicated preview branch (reused from bootstrap.py --branch)
MongoDB per-preview database <id>_test (MONGO_DATABASE), plus prefixed tenant ids
Neo4j per-preview tenantId namespace (IX_TENANT_PREFIX, applied by the shared tenant sanitizers) — logical, shares the test instance
Redis loader timestamp and FAQ-sync fingerprint keys carry IX_TENANT_PREFIX — shares the test instance
Config per-preview Secret Manager secret cloned from rose-backend-env-test

Teardown deletes the preview jobs and the preview secret, and deletes the Supabase branch (pass --keep-supabase-branch to leave it for the supabase-branch-cleanup / -reaper workflows). Neo4j nodes are swept with MATCH (n) WHERE n.tenantId STARTS WITH '<id>__' DETACH DELETE n; preview Redis keys match *:test:<id>__*; the Mongo database is <id>_test. Teardown prints these sweeps but does not run them. Atlas accepts only the cloud NAT IP, so drop the database from a cloud job or the Atlas UI.

Teardown leaves the local .env.local files pointing at the deleted branch. From the repository root, run python3.12 scripts/supabase_branch.py unwire to return local services to the shared project.

Neo4j isolation is logical, not physical

Previews share the test Neo4j instance (Aura, no CREATE DATABASE). A code path that ignores tenantId could read or write test data. Acceptable for functional testing; verify graph changes with Knowledge change verification.

Scope

Implemented component: job:document-loader. Register more jobs in the JOBS table in scripts/preview/preview.py. Previewing shared -test services (a tagged no-traffic revision of search-test-test etc.) was intentionally not shipped: gcloud run deploy on a shared service pins its traffic off LATEST and leaves the preview image/secret as its latest template, which can freeze or contaminate the shared test env. If a live API-URL preview is needed later, deploy it as a dedicated throwaway service, not a revision of the shared one.