# sandbox.neurontechnologies.ai Internal-only experimentation environment. The real soul (with safety stripped) runs here when the build pipeline lands. Sandbox is intentionally separate from stage so the stage path stays clean for normal QA work. The terraform itself lives at `servers/gcp/sandbox.tf` (single file, mirrors the existing flat-file layout under `servers/gcp/`). This directory holds documentation only. ## What is sandbox | Layer | Where | |------------------|-------------------------------------------------------------| | Domain | `sandbox.neurontechnologies.ai` | | Cloud Run | `neuron-sandbox-us` (us-central1, single region) | | Image registry | `us-central1-docker.pkg.dev/neuron-785695/neuron-sandbox/` | | Service account | `neuron-sandbox@neuron-785695.iam.gserviceaccount.com` | | Access control | Cloudflare Zero Trust Access, single rule | | Identity rule | `email_domain == neurontechnologies.ai` | | Supabase | Separate project (NOT a schema in prod's project) | | Secret Manager | `sandbox-supabase-{url,anon-key,service-key}` | ## How to get in 1. Sign in with your `@neurontechnologies.ai` Google account at `https://sandbox.neurontechnologies.ai/`. Cloudflare Access intercepts the request, redirects you to the Google IdP, and only allows `email_domain == neurontechnologies.ai` through. 2. After auth, you land on the running stub. With the stub deployed, the root response is: ```json {"env":"sandbox","status":"ready","soul":"not_loaded"} ``` 3. There are no service tokens, no mTLS certs, no bypass paths. The single policy is the entire access surface. Verify the lockdown from a shell at any time: ```bash curl -sI https://sandbox.neurontechnologies.ai/ | head -2 # HTTP/2 302 # location: https://neuralplatform.cloudflareaccess.com/cdn-cgi/access/login/sandbox.neurontechnologies.ai?... ``` If the first line is anything other than 302 to the cloudflareaccess domain, something has broken. Treat it as a P0. ## What's NOT in sandbox - **No outbound email.** `RESEND_API_KEY` is intentionally not mounted on `neuron-sandbox-us`. Defense-in-depth against an experimental soul accidentally emailing real users. - **No public share endpoints.** `/api/share`, `/said`, and `/share/` return `410 Gone` with `{"error":"not_available_in_sandbox"}`. Public artifact distribution stays in prod. - **No prod data.** Sandbox uses an isolated Supabase project. Nothing in the prod Supabase is reachable from the sandbox SA. - **No prod IAM.** The `neuron-sandbox` SA has zero project-level role bindings. Its only grants are `roles/secretmanager.secretAccessor` on the three sandbox-specific secrets. Verify with: ```bash # As neuron-sandbox SA, all of these MUST return PERMISSION_DENIED: gcloud secrets versions access latest --secret=stripe-secret-key gcloud secrets versions access latest --secret=anthropic-api-key gcloud secrets versions access latest --secret=supabase-service-key gcloud run services describe marketing-prod-us --region=us-central1 ``` - **No prod LLM credentials.** Sandbox does NOT mount `anthropic-api-key`. When the real soul lands, it will need its own sandbox-only LLM key (cheaper key, lower rate limits) provisioned and granted to the sandbox SA. ## Provision the isolated Supabase project (one-time, manual) There is no Supabase Management API token in `~/Secrets/api-keys/` or in GCP Secret Manager today, so the project creation is a manual step. 1. Sign in to https://supabase.com/dashboard with the Neuron Technologies org owner account. 2. New project. Name: `neuron-sandbox`. Region: `us-east-1` (closest to us-central1 Cloud Run latency-wise; single-region experimental env, so geo doesn't matter much). 3. After provisioning, capture from Project Settings -> API: - Project URL: `https://.supabase.co` - `anon` public key - `service_role` secret key 4. Push them into Secret Manager (this overwrites the placeholder versions that terraform created): ```bash echo -n 'https://.supabase.co' | gcloud secrets versions add \ sandbox-supabase-url --data-file=- --project=neuron-785695 echo -n '' | gcloud secrets versions add \ sandbox-supabase-anon-key --data-file=- --project=neuron-785695 echo -n '' | gcloud secrets versions add \ sandbox-supabase-service-key --data-file=- --project=neuron-785695 ``` 5. Force a new sandbox revision so it picks up the secrets: ```bash gcloud run services update neuron-sandbox-us --region=us-central1 \ --project=neuron-785695 \ --image=us-central1-docker.pkg.dev/neuron-785695/neuron-sandbox/sandbox:initial ``` 6. Do NOT seed any tables in this project yet. Real soul might want a different schema than the demo soul. Schema lands when real soul lands. When a Supabase Management API token does become available, automate this by writing a small script in `scripts/` that calls `POST https://api.supabase.com/v1/projects` and immediately writes the returned values back to Secret Manager. Track that as a follow-up. ## Deploy a new build (manual, until CI is wired) ```bash cd ~/Development/neuron-technologies/products/sandbox-stub # or real soul source bash ./build.sh # cross-compile + push to AR gcloud run services update neuron-sandbox-us \ --region=us-central1 \ --project=neuron-785695 \ --image=us-central1-docker.pkg.dev/neuron-785695/neuron-sandbox/sandbox: ``` The terraform `google_cloud_run_v2_service.neuron_sandbox` has `lifecycle.ignore_changes = [template[0].containers[0].image]` so CI rollouts won't be undone by the next `terraform apply`. CI wiring lands as part of the real-soul-build follow-up backlog item (see `mcp__neuron__planWork` for the tracked item). ## Disaster recovery Sandbox is disposable. If state gets weird, nuke it and rebuild: ```bash cd ~/Development/infrastructure/servers/gcp terraform destroy \ -target=cloudflare_zero_trust_access_policy.sandbox_only_neurontech \ -target=cloudflare_zero_trust_access_application.sandbox \ -target=cloudflare_record.sandbox \ -target=google_cloud_run_domain_mapping.sandbox \ -target=google_cloud_run_v2_service_iam_member.neuron_sandbox_public \ -target=google_cloud_run_v2_service.neuron_sandbox \ -target=google_secret_manager_secret_iam_member.sandbox_url_accessor \ -target=google_secret_manager_secret_iam_member.sandbox_anon_accessor \ -target=google_secret_manager_secret_iam_member.sandbox_service_accessor \ -target=google_secret_manager_secret_version.sandbox_supabase_url_placeholder \ -target=google_secret_manager_secret_version.sandbox_supabase_anon_key_placeholder \ -target=google_secret_manager_secret_version.sandbox_supabase_service_key_placeholder \ -target=google_secret_manager_secret.sandbox_supabase_url \ -target=google_secret_manager_secret.sandbox_supabase_anon_key \ -target=google_secret_manager_secret.sandbox_supabase_service_key \ -target=google_service_account.neuron_sandbox \ -target=google_artifact_registry_repository.sandbox # Then re-apply: terraform apply ``` If the 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/sandbox.tf` | All sandbox terraform | | `servers/gcp/sandbox/README.md` | This document | | `~/Development/neuron-technologies/products/sandbox-stub/` | Stub source code (Go, 4 routes) |