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
tenantIdnamespace,
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 (
JOBSinpreview.py); the preview runs as its own<job>-<id>resource. An arbitrary or-productionname is not a valid component. IX_ENVIRONMENT=test. Enum-keyed logic (including the production-write guard) stays ontest; writes land in the PR's Supabase branch and<id>Mongo/Neo4j namespaces, never production.- Reversible + self-cleaning.
cloud-preview-downremoves 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):
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.