# dev.neurontechnologies.ai Team-internal free-fire iteration zone. Looser standards than stage: broken builds OK, no public users, no real PII, no live Stripe, no real outbound email. Auto-deploys on every push to the `dev` branch once the CI/CD runner agent ships (this directory provisions the shell + initial deploy only). The terraform itself lives at `servers/gcp/marketing-dev.tf` (single file, mirrors the existing flat-file layout under `servers/gcp/`). This directory holds documentation only. ## What is dev | Layer | Where | |------------------|------------------------------------------------------------------| | Domain | `dev.neurontechnologies.ai` | | Cloud Run | `marketing-dev-us` (us-central1, single region) | | Image registry | `us-central1-docker.pkg.dev/neuron-785695/neuron-marketing/` | | Initial image | `marketing:fix-gallery-render-1236` (matched stage at provision) | | Service account | `neuron-marketing-dev@neuron-785695.iam.gserviceaccount.com` | | Access control | Cloudflare Zero Trust Access, two policies | | Identity rule 1 | `email_domain == neurontechnologies.ai` | | Identity rule 2 | named externals via `dev_external_collaborators` variable | | Supabase | NEW dev project (NOT shared with stage or prod) | | LLM | `claude-haiku-4-5` (cheap) | | Stripe | Test mode (same test keys as stage) | | Email | `RESEND_API_KEY` mounted; `RESEND_DRY_RUN=1` short-circuits send | ## How dev differs from stage | Property | stage | dev | |------------------|--------------------------------|-------------------------------------------| | `NODE_ENV` | `staging` | `development` | | LLM model | `claude-sonnet-4-5` | `claude-haiku-4-5` | | Supabase | shares prod project | dedicated `neuron-dev` project (isolated) | | Email | live (real sends) | `RESEND_DRY_RUN=1` (no real sends) | | Stripe | test mode | test mode (same) | | Auto-deploy | manual | every push to `dev` branch (post CI wire) | | Allowed users | team + Tim's gmail | team + named external collaborators | ## How to get in 1. Sign in with your `@neurontechnologies.ai` Google account at `https://dev.neurontechnologies.ai/`. Cloudflare Access intercepts the request, redirects you to the Google IdP, and only allows `email_domain == neurontechnologies.ai` plus any allowlisted externals through. 2. After auth, you land on the running marketing site exactly as it would render in stage / prod, but with `NODE_ENV=development`. Verify the lockdown from a shell at any time: ```bash curl -sI https://dev.neurontechnologies.ai/ | head -2 # HTTP/2 302 # location: https://neuralplatform.cloudflareaccess.com/cdn-cgi/access/login/dev.neurontechnologies.ai?... ``` If the first line is anything other than 302 to the cloudflareaccess domain, something has broken. Treat it as a P0. ## Provision the dev Supabase project (one-time, automated) The Supabase Management API token lives in Vault at `secret/neuron-technologies/supabase` (field `access_token`). ```bash # Pull the token export SBP_TOKEN=$(vault kv get -field=access_token secret/neuron-technologies/supabase) # Create the project ORG_ID=$(curl -s -H "Authorization: Bearer $SBP_TOKEN" \ https://api.supabase.com/v1/organizations | jq -r '.[0].id') curl -s -X POST -H "Authorization: Bearer $SBP_TOKEN" \ -H "Content-Type: application/json" \ https://api.supabase.com/v1/projects \ -d "{\"name\":\"neuron-dev\",\"region\":\"us-west-2\",\"organization_id\":\"$ORG_ID\",\"db_pass\":\"$(openssl rand -hex 24)\"}" | jq . # Capture project ref, URL, anon_key, service_role_key from the response. ``` After provisioning, write the values into Secret Manager (this overwrites the placeholder versions terraform created): ```bash echo -n 'https://.supabase.co' | gcloud secrets versions add \ dev-supabase-url --data-file=- --project=neuron-785695 echo -n '' | gcloud secrets versions add \ dev-supabase-anon-key --data-file=- --project=neuron-785695 echo -n '' | gcloud secrets versions add \ dev-supabase-service-key --data-file=- --project=neuron-785695 ``` Then apply the prod schema (5 tables + indexes + 1 backfill) to dev via the Supabase Management API SQL endpoint: ```bash DEV_REF= for f in ~/Development/neuron-technologies/products/web/migrations/*.sql; do echo "=== applying $f ===" jq -Rs '{query: .}' < "$f" | curl -s -X POST \ -H "Authorization: Bearer $SBP_TOKEN" \ -H "Content-Type: application/json" \ "https://api.supabase.com/v1/projects/$DEV_REF/database/query" \ -d @- done ``` Force a new dev revision so it picks up the secrets: ```bash gcloud run services update marketing-dev-us --region=us-central1 \ --project=neuron-785695 \ --image=us-central1-docker.pkg.dev/neuron-785695/neuron-marketing/marketing:fix-gallery-render-1236 ``` ## Manual deploy (until CI is wired) ```bash gcloud run services update marketing-dev-us \ --region=us-central1 \ --project=neuron-785695 \ --image=us-central1-docker.pkg.dev/neuron-785695/neuron-marketing/marketing: ``` The terraform `google_cloud_run_v2_service.marketing_dev_us` has `lifecycle.ignore_changes = [template[0].containers[0].image]` so CI rollouts will not be undone by the next `terraform apply`. CI wiring lands once the CI/CD runner agent (commit a9bfb7d) ships. The follow-up backlog item is tracked in Neuron. ## What NOT to do in dev - Do NOT use real PII. Anything entered here can be wiped without notice. - Do NOT use live Stripe keys. Test mode only. - Do NOT expect persistent data. Dev Supabase is disposable. - Do NOT ship live transactional email. `RESEND_DRY_RUN=1` is the contract; if `main.el`'s `send_email` helper does not honor it yet, the unverified `dev.neurontechnologies.ai` envelope-from will cause Resend to reject the send anyway. Either way: no real inbox is reached. Track that work via the Neuron backlog. - Do NOT grant the dev SA any prod IAM. Resource isolation is the only thing keeping dev experiments from corrupting prod. ## Disaster recovery Dev is disposable. If state gets weird, nuke it and rebuild: ```bash cd ~/Development/infrastructure/servers/gcp terraform destroy \ -target=cloudflare_zero_trust_access_policy.marketing_dev_external \ -target=cloudflare_zero_trust_access_policy.marketing_dev_team \ -target=cloudflare_zero_trust_access_application.marketing_dev \ -target=cloudflare_record.marketing_dev \ -target=google_cloud_run_domain_mapping.marketing_dev \ -target=google_cloud_run_v2_service_iam_member.marketing_dev_us_public \ -target=google_cloud_run_v2_service.marketing_dev_us \ -target=google_secret_manager_secret_version.dev_supabase_url_placeholder \ -target=google_secret_manager_secret_version.dev_supabase_anon_key_placeholder \ -target=google_secret_manager_secret_version.dev_supabase_service_key_placeholder \ -target=google_secret_manager_secret_version.dev_stripe_price_professional_placeholder \ -target=google_secret_manager_secret_version.dev_stripe_price_founding_placeholder \ -target=google_secret_manager_secret_version.dev_stripe_price_family_child_placeholder \ -target=google_secret_manager_secret.dev_supabase_url \ -target=google_secret_manager_secret.dev_supabase_anon_key \ -target=google_secret_manager_secret.dev_supabase_service_key \ -target=google_secret_manager_secret.dev_stripe_price_professional \ -target=google_secret_manager_secret.dev_stripe_price_founding \ -target=google_secret_manager_secret.dev_stripe_price_family_child \ -target=google_project_iam_member.marketing_dev_secret_accessor \ -target=google_service_account.marketing_dev # Then re-apply: terraform apply ``` If the dev Supabase project was provisioned, also delete it from the Supabase dashboard and re-create with a new name. The Secret Manager secrets carry forward as placeholders; rewrite them with the new project's values. ## Files | File | What | |---------------------------------------------------------|-----------------------------------| | `servers/gcp/marketing-dev.tf` | All marketing-dev terraform | | `servers/gcp/marketing-dev/README.md` | This document |