Commit Graph

244 Commits

Author SHA1 Message Date
will.anderson d228701828 Merge pull request 'dev → stage: comprehensive checkout + Stripe tests' (#76) from dev into stage
Merge: dev into stage — comprehensive checkout + Stripe tests
2026-05-11 06:19:33 +00:00
will.anderson c0e6b40a5a Merge pull request 'Comprehensive checkout + Stripe payment flow tests' (#75) from feat/checkout-comprehensive-tests into dev
Merge: Comprehensive checkout + Stripe payment flow tests
2026-05-11 06:19:19 +00:00
will.anderson dbb8035698 Add comprehensive checkout + Stripe payment flow tests
- checkout-flows.spec.ts: 60 tests covering all 3 plan variants (free/
  professional/founding), auth section visibility, form validation,
  sign-in/sign-up toggle, mocked Supabase auth flows (sign-up, email-
  confirm-required, existing session, sign-in error), DOM transitions
  (auth-section → payment-section, free-success panel), auth badge
  content + email pre-fill, /api/checkout and /api/supabase-config
  endpoint contracts, CORS enforcement

- checkout-stripe.spec.ts: 45 tests covering Stripe.js presence,
  NEURON_CFG shape, submit-btn disabled state, founding attestation
  checkbox + attest-warn guard, professional charge timing radios,
  setup_mode label, mocked full Stripe payment flow via addInitScript +
  /api/payment-intent intercept, submit validation (name/email),
  decline handling, sold-out guard, /api/payment-intent /api/link-
  customer /api/attest /api/founding-count endpoint contracts, and
  live test-card flows (skipped unless STRIPE_LIVE=1)

Mocking strategy: page.route() for /api/supabase-config + Supabase
auth endpoints; addInitScript() for window.Stripe mock; localStorage
pre-seeding for existing-session tests.
2026-05-11 01:18:37 -05:00
will.anderson 41f27e83aa Merge pull request 'test: full Playwright + API test suite for stage' (#74) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 6m43s
Merge dev into stage
2026-05-11 05:29:33 +00:00
will.anderson 83aa7ad64f Merge pull request 'test: full Playwright + API test suite for stage' (#73) from fix/checkout-auth-reveal into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m59s
Merge fix/checkout-auth-reveal into dev
2026-05-11 05:29:16 +00:00
will.anderson cac7bd5727 test: full Playwright + API test suite for stage
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m52s
159 tests across three Playwright projects (api, chromium, mobile):
- tests/api/security.test.ts: security headers, CORS on /api/supabase-config
  (origin allowlist enforced), auth gate on /api/demo, Stripe webhook
  signature enforcement, source file leakage, path traversal, input
  validation (8000-char message cap)
- tests/api/endpoints.test.ts: /api/health, /api/founding-count shape
  invariants, /api/supabase-config JWT shape, sitemap.xml, robots.txt,
  /llms.txt, /api/soul-health internal gate, 404 for unknown routes
- tests/e2e/landing.spec.ts: title, h1 count, meta description, OG tags,
  canonical (no stage leak), JSON-LD schema, demo widget DOM presence,
  JS error filtering (known GTM/CSP noise excluded)
- tests/e2e/seo.spec.ts: per-page title patterns, noindex on checkout,
  canonical URLs, sitemap production-URL enforcement
- tests/e2e/checkout.spec.ts: all three plan variants, auth section, payment
  element, canonical
- tests/e2e/chat.spec.ts: widget DOM structure, auth gate (send button
  disabled without session), API-level auth rejection
- tests/e2e/navigation.spec.ts: all public routes return 200, 404s for
  removed/old paths (/terms, /enterprise-terms, /gallery), static files

All 159 pass against stage. CI step added to stage.yaml after smoke test.
2026-05-11 00:28:33 -05:00
will.anderson 533436e2c2 Merge pull request 'security: pentest fixes — deploy to stage' (#70) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m32s
security: pentest fixes — deploy to stage
2026-05-11 04:57:20 +00:00
will.anderson e914704d86 Merge pull request 'security: pentest fixes — webhook sig, CORS, soul-health gate, asset headers' (#69) from fix/checkout-auth-reveal into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m31s
security: pentest fixes — webhook sig, CORS, soul-health gate, asset headers
2026-05-11 04:57:02 +00:00
will.anderson 43e1245306 seo: full audit fixes — meta, og, schema, canonical, sitemap, headings, alts
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m57s
- Per-page title/description/canonical/OG tags: about, checkout (per-plan),
  terms, enterprise-terms, success all get unique SEO blocks
- Homepage title updated to em-dash form; meta description adds CTA
- og:site_name added to all pages
- noindex/nofollow on checkout, success, account pages
- Sitemap (/sitemap.xml) with all public pages; robots.txt updated with
  Sitemap directive and Disallow for private paths
- Schema: WebSite type added, Organization gains logo ImageObject, SoftwareApplication
  gains url field, billingIncrement corrected to billingPeriod (ISO 8601 P1M),
  sameAs gains x.com/neurontechai alongside GitHub
- marked.min.js given defer attribute (was render-blocking)
- page_head refactored into page_head_base + page_seo_block + page_open_seo
  for clean inner-page overrides without duplicating the CSS/script block
2026-05-10 23:56:40 -05:00
will.anderson 3f3c5cf149 security: penetration test fixes — headers, cors, path traversal, info leakage
- Switch to http_serve_v2/http_set_handler_v2 so request headers are available
  to El handler code (prerequisite for all header-based security checks)

- Stripe webhook (CVE-class): add HMAC-SHA256 signature verification against
  Stripe-Signature header using STRIPE_WEBHOOK_SECRET env var. Previously any
  unauthenticated POST could forge a payment_intent.succeeded event and
  increment the founding counter or trigger Supabase account provisioning for
  arbitrary emails.

- CORS on /api/supabase-config: restrict to neurontechnologies.ai and localhost
  origins only. Cross-origin requests now get 403.

- /api/soul-health: require X-Internal: true header; otherwise return 404.
  Endpoint was publicly accessible and leaked internal soul service URL,
  network topology, and raw probe responses.

- Static asset / JS headers: add X-Frame-Options, Referrer-Policy,
  Permissions-Policy, and Content-Security-Policy to static_asset_headers_json
  and js_headers_json. These were only present on HTML/API responses before.

- Fix state key bug: share_card_page read state_get("__neuron_origin__") but
  the key registered at startup is "__origin__", causing empty base URLs in
  share card og: meta tags.
2026-05-10 23:56:31 -05:00
will.anderson aeea037e6f Merge pull request 'feat: auth-gate demo chat + budget circuit breaker' (#68) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m53s
feat: auth-gate demo chat + budget circuit breaker
2026-05-11 04:45:56 +00:00
will.anderson bdff0ad153 Merge pull request 'feat: auth-gate demo chat + budget circuit breaker' (#67) from fix/checkout-auth-reveal into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m27s
feat: auth-gate demo chat + budget circuit breaker
2026-05-11 04:45:36 +00:00
will.anderson fe418bf3f7 feat: auth-gate demo chat + budget circuit breaker
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 2m10s
Gate the demo chat behind Supabase auth: the widget now fetches Supabase
config on open, shows a compact sign-in pane (Google OAuth or email/password)
when the user is unauthenticated, and passes the access_token to /api/demo.
The server verifies the token via supabase_auth_user() before any processing
and uses the verified user ID as the rate-limit key.

Add a budget kill switch: a demo_config table in Supabase holds a
demo_enabled flag that /api/demo polls every 60s (cached, fails open).
A Cloud Function (demo-budget-guard) is triggered by a GCP Pub/Sub budget
alert and sets demo_enabled = 'false' when spend crosses 90% of the $150
daily budget. Budget and topic are provisioned; function is live in
us-central1.
2026-05-10 23:44:54 -05:00
will.anderson 41bad94368 Merge pull request 'feat: scale fixes — max-instances, asset caching, shared rate limits, global cap' (#66) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 4m15s
feat: scale fixes — max-instances, asset caching, shared rate limits, global cap
2026-05-11 03:12:54 +00:00
will.anderson 7536c216e6 Merge pull request 'feat: scale fixes — max-instances, asset caching, shared rate limits, global cap' (#65) from fix/checkout-auth-reveal into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m30s
feat: scale fixes — max-instances, asset caching, shared rate limits, global cap
2026-05-11 03:12:30 +00:00
will.anderson bdb6ddc581 feat: scale fixes — max-instances, asset caching, shared rate limits, global cap
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 2m0s
- soul-demo-stage: raise max-instances 10 → 50
- marketing-stage: explicitly set max-instances 200
- /assets/* and /brand/*: return Cache-Control: public, max-age=31536000, immutable
  so Cloudflare caches static assets at the edge (eliminates Cloud Run hit per request)
- /js/*: bump from max-age=3600 to max-age=31536000, immutable (same policy)
- Per-uid demo rate limit: replace in-process state with Supabase demo_rate_limits table
  so the 10-chats/day cap is enforced across all Cloud Run instances; falls back to
  in-process for local dev when SUPABASE_SERVICE_KEY is absent
- Global circuit breaker: trip if any single instance handles ≥2000 demo requests/UTC day
2026-05-10 22:12:09 -05:00
will.anderson 3020b4e902 Merge pull request 'feat: extract soul-demo into standalone Cloud Run service' (#64) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 4m5s
feat: extract soul-demo into standalone Cloud Run service
2026-05-11 02:09:27 +00:00
will.anderson 00f05f813e Merge pull request 'feat: extract soul-demo into standalone Cloud Run service' (#63) from fix/checkout-auth-reveal into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m33s
feat: extract soul-demo into standalone Cloud Run service
2026-05-11 02:09:06 +00:00
will.anderson 93f9ea2be2 feat: extract soul-demo into standalone Cloud Run service
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 2m19s
2026-05-10 21:08:46 -05:00
will.anderson e82425a829 Merge pull request 'deploy: fix HAVE_CURL verification' (#62) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m14s
2026-05-11 01:07:49 +00:00
will.anderson e480aba2f1 Merge pull request 'fix: HAVE_CURL verification — use strings not nm' (#61) from fix/checkout-auth-reveal into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m25s
2026-05-11 01:07:39 +00:00
will.anderson feee40c34b ci: fix HAVE_CURL verification — use strings check not nm
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m54s
2026-05-10 20:07:21 -05:00
will.anderson c4cdb31529 Merge pull request 'deploy: fix HAVE_CURL — enable chat proxy to soul-demo' (#60) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 57s
deploy: fix HAVE_CURL — enable chat proxy to soul-demo
2026-05-11 01:03:41 +00:00
will.anderson e6e89a1f4d Merge pull request 'fix: relink neuron-web with HAVE_CURL (chat proxy)' (#59) from fix/checkout-auth-reveal into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m15s
fix: relink neuron-web with HAVE_CURL (chat proxy)
2026-05-11 01:03:22 +00:00
will.anderson 8b8cb2f580 ci: relink neuron-web with HAVE_CURL after elb build
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m45s
elb does not pass -DHAVE_CURL when compiling el_runtime.c, so all
http_get/http_post calls from El code return the no-op error string
instead of making real HTTP requests. This breaks the chat proxy to
soul-demo at localhost:7772.

After elb runs (and generates all intermediate .c files in dist/),
recompile el_runtime.c with -DHAVE_CURL and relink the entire binary
from those generated files. Verifies curl_easy_init is present in the
output binary before proceeding.
2026-05-10 20:03:00 -05:00
will.anderson a1c0cc090d Merge pull request 'Deploy: replace k3s with direct soul-demo watchdog' (#58) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 2m54s
Merge dev into stage
2026-05-11 00:47:16 +00:00
will.anderson 4d359ff021 Merge pull request 'Replace k3s with direct soul-demo watchdog' (#57) from fix/checkout-auth-reveal into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m26s
Merge fix/checkout-auth-reveal into dev
2026-05-11 00:46:56 +00:00
will.anderson cd1c6737e8 Replace k3s with direct soul-demo watchdog in Cloud Run container
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 2m11s
Cloud Run gen2 doesn't provide eth0 with a unicast IP, causing k3s flannel
to crash on every container start. k3s was also wrong architecture for
Cloud Run (HPA inside a container, k3s overhead for one process).

Changes:
- entrypoint.sh: replace k3s server with a bash watchdog loop that starts
  soul-demo directly and restarts it on crash (3s backoff)
- Dockerfile.stage: remove k3s binary, soul-demo-image.tar, k3s manifests
  and their associated dirs/envvars; keep soul-demo binary only
- stage.yaml: remove 'Download k3s binary' step; rename and simplify
  soul-demo build step to compile binary only (no OCI image/tar)
- dev.yaml: update soul-demo placeholder step (binary not tar)
- manifest.el: document HAVE_CURL requirement since manifest.el has no
  c_flags/link_flags directive support
2026-05-10 19:46:35 -05:00
will.anderson 7df96a2273 Merge pull request 'Deploy: fix envelope truncation' (#56) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m50s
2026-05-11 00:23:38 +00:00
will.anderson f27fc2622c Merge pull request 'Fix envelope truncation in http_response when called after fs_read' (#55) from fix/checkout-auth-reveal into dev
Dev — Build & local smoke test / build-smoke (push) Failing after 2m46s
2026-05-11 00:23:28 +00:00
will.anderson 0433fe8c0f Fix http_response() truncating envelope via stale _tl_fs_read_len
Dev — Build & local smoke test / build-smoke (pull_request) Failing after 2m22s
http_response() builds a JSON envelope wrapping the body. If the caller
previously called fs_read() (which sets _tl_fs_read_len = file_size),
http_worker used that stale value as the response copy length — truncating
the larger envelope to the original file size before it reached
http_send_response. The truncated envelope had the body field cut mid-string;
jp_parse_string_raw failed, env_body = "", and http_send_all sent file_size
bytes of garbage past the empty string.

Fix: reset _tl_fs_read_len = 0 at the start of http_response(). The hint
was set for the raw file bytes; the envelope is a new string and must use
strlen() for its length.
2026-05-10 19:23:10 -05:00
will.anderson d3b890b739 Merge pull request 'Deploy: fix JS served as JSON envelope' (#54) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m11s
Deploy: fix JS served as JSON envelope
2026-05-10 22:34:57 +00:00
will.anderson 9da4d50883 Merge pull request 'Fix JS files served as JSON envelope (checkout/Stripe/auth all broken)' (#53) from fix/checkout-auth-reveal into dev
Dev — Build & local smoke test / build-smoke (push) Failing after 2m4s
Fix JS files served as JSON envelope (checkout/Stripe/auth all broken)
2026-05-10 22:34:32 +00:00
will.anderson c99ca82302 Fix JS files served as raw JSON envelope instead of JavaScript
Dev — Build & local smoke test / build-smoke (pull_request) Failing after 1m36s
http_parse_envelope() called json_parse() on the entire response envelope
(~47KB when body is obfuscated JS). The parser failed on large/complex content,
so is_envelope=0 and the raw JSON was sent — browsers got {"el_http_response":1,...}
instead of executable JavaScript, silently breaking all client-side code.

Fix: replace json_parse-of-full-envelope with a direct field scanner:
- "status" extracted via strtol
- "headers" object extracted via brace-depth scan, then json_parse only that
  small substring (always safe — headers are simple k/v string pairs < 1KB)
- "body" string extracted via jp_parse_string_raw — no intermediate allocation

Also: /js/* route now returns http_response(200, js_headers_json(), content)
with explicit Content-Type: application/javascript so the browser doesn't
apply the json-heuristic (obfuscated JS starting with '[' was detected as JSON,
which with X-Content-Type-Options: nosniff blocks script execution).
2026-05-10 17:32:45 -05:00
will.anderson 3f069eeb79 Merge pull request 'Fix checkout auth (dev → stage)' (#52) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m14s
Fix checkout auth (dev → stage)
2026-05-10 22:01:17 +00:00
will.anderson e292453905 Merge pull request 'Fix checkout auth: free-success panel + Stripe auto-init for paid plans' (#51) from fix/checkout-auth-reveal into dev
Dev — Build & local smoke test / build-smoke (push) Failing after 2m12s
Fix checkout auth: free-success panel + Stripe auto-init for paid plans
2026-05-10 22:00:55 +00:00
will.anderson 0263e51407 Fix checkout: show free-success when logged in; init Stripe without auth on paid plans
Dev — Build & local smoke test / build-smoke (pull_request) Failing after 1m47s
- revealPaymentForm: for free plan, show #free-success panel (was doing nothing,
  leaving page blank when user already had a Supabase session)
- checkExistingSession: for paid plans with no session, call initStripe immediately —
  auth is optional, the payment form shouldn't wait indefinitely
- Guard _formRevealed: prevent double-call from handleAuthRedirect + checkExistingSession
2026-05-10 16:59:51 -05:00
will.anderson 8676751ed6 Merge pull request 'Fix http handler registration (dev → stage)' (#50) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m40s
Merge PR #50: Fix http handler registration (dev → stage)
2026-05-10 18:37:16 +00:00
will.anderson b4935ed880 Merge pull request 'Fix http handler not found: pre-register via constructor' (#49) from fix/entrypoint-k3s-nonblocking into dev
Dev — Build & local smoke test / build-smoke (push) Failing after 2m29s
Merge PR #49: Fix http handler not found
2026-05-10 18:36:47 +00:00
will.anderson 9a6f0defd1 Fix http handler not found: pre-register via el_runtime_register_handler
Dev — Build & local smoke test / build-smoke (pull_request) Failing after 1m54s
elb links without -rdynamic so dlsym(RTLD_DEFAULT, "handle_request")
returns NULL at runtime. http_set_handler stores the name as active but
never finds a function pointer, causing every request to return
"el-runtime: no http handler registered" even after http_serve is called.

Fix: add a __attribute__((constructor)) in web_stubs.c that calls
el_runtime_register_handler("handle_request", handle_request) directly,
bypassing dlsym entirely. The handler is in the registry before main()
runs, so http_lookup_active() finds it on the first request.
2026-05-10 13:36:05 -05:00
will.anderson a4f5312069 Merge pull request 'Fix GLIBC_2.38 mismatch: switch base image to ubuntu:24.04' (#48) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m37s
Fix GLIBC_2.38 mismatch: switch base image to ubuntu:24.04
2026-05-10 18:02:14 +00:00
will.anderson ee0147869e Merge pull request 'Fix GLIBC_2.38 mismatch: switch base image to ubuntu:24.04' (#47) from fix/entrypoint-k3s-nonblocking into dev
Dev — Build & local smoke test / build-smoke (push) Failing after 2m23s
Fix GLIBC_2.38 mismatch: switch base image to ubuntu:24.04
2026-05-10 18:01:57 +00:00
will.anderson 740382fca1 Fix GLIBC_2.38 mismatch: switch base image to ubuntu:24.04
Dev — Build & local smoke test / build-smoke (pull_request) Failing after 2m13s
CI runner (Ubuntu 24.04, glibc 2.39) produces binaries that require
GLIBC_2.38+. debian:bookworm-slim ships glibc 2.36 which doesn't have
the GLIBC_2.38 versioned symbols — container crashes immediately with
"version GLIBC_2.38 not found". Switch to ubuntu:24.04 (glibc 2.39)
to match the build environment. Also updates libcurl4/libssl3 package
names to their Ubuntu 24.04 canonical t64 forms.
2026-05-10 13:01:38 -05:00
will.anderson c76e5a19eb Merge pull request 'Non-blocking entrypoint + k3s --flannel-iface fix' (#46) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 4m8s
Non-blocking entrypoint + k3s flannel-iface fix
2026-05-10 17:55:12 +00:00
will.anderson 25f6631049 Merge pull request 'Non-blocking entrypoint: start neuron-web before k3s is ready' (#45) from fix/entrypoint-k3s-nonblocking into dev
Dev — Build & local smoke test / build-smoke (push) Failing after 2m43s
Non-blocking entrypoint: start neuron-web before k3s is ready
2026-05-10 17:54:54 +00:00
will.anderson 180acc92a0 Non-blocking entrypoint: start neuron-web before k3s is ready
Dev — Build & local smoke test / build-smoke (pull_request) Failing after 2m11s
k3s fails to start in Cloud Run gen2 with "unable to select an IP from
default routes" because Cloud Run's network sandbox doesn't expose a
standard default route for k3s to detect. The blocking wait on k3s
prevented neuron-web from ever binding port 8080, causing Cloud Run's
startup probe to time out and terminate the container.

Two changes:
1. Add --flannel-iface=eth0 so k3s pins to Cloud Run's eth0 rather than
   walking the routing table to detect a default-route interface.
2. Start neuron-web immediately after launching k3s in background.
   soul-demo becomes available asynchronously; neuron-web handles it
   being temporarily unavailable gracefully.
2026-05-10 12:54:26 -05:00
will.anderson 58b7b32cdd Single-stage Dockerfile.stage: pre-download k3s on host runner
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 12s
2026-05-10 16:27:07 +00:00
will.anderson 689062fc87 Single-stage Dockerfile.stage: pre-download k3s on host runner
Dev — Build & local smoke test / build-smoke (push) Failing after 1m20s
2026-05-10 16:26:46 +00:00
will.anderson e6fd110073 Single-stage Dockerfile.stage: pre-download k3s on host runner
Dev — Build & local smoke test / build-smoke (pull_request) Failing after 1m37s
The multi-stage Docker builder (which installed build-essential, compiled
soul-demo, and downloaded k3s inside Docker) was causing RWLayer nil
corruption on the runner's overlay2 driver. Every affected run failed at
apt-get install in the runtime stage after the builder stage completed.

Fix: move k3s download to the CI host runner (same pattern as soul-demo
compilation, which now passes reliably). Dockerfile.stage becomes single-
stage: no apt-get in a builder stage, no network downloads, just COPY of
pre-built binaries. Also adds --no-cache to the main docker build for
consistency with the soul-demo step fix.
2026-05-10 11:26:23 -05:00
will.anderson 0fdabcce86 Merge pull request 'promote: dev → stage' (#42) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 4m46s
promote: dev → stage
2026-05-10 15:57:36 +00:00