Files
..

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:
    {"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:

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/<id> 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:
    # 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://<ref>.supabase.co
    • anon public key
    • service_role secret key
  4. Push them into Secret Manager (this overwrites the placeholder versions that terraform created):
    echo -n 'https://<ref>.supabase.co' | gcloud secrets versions add \
      sandbox-supabase-url --data-file=- --project=neuron-785695
    echo -n '<anon-key>' | gcloud secrets versions add \
      sandbox-supabase-anon-key --data-file=- --project=neuron-785695
    echo -n '<service-key>' | 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:
    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)

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:<tag>

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:

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)