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
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.
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.
k3s needs CAP_SYS_ADMIN to create network namespaces and mount cgroups.
USER landing was preventing this. Cloud Run gen2 is the security boundary.
60% CPU was too conservative for soul-demo — it is I/O-bound (LLM API calls),
not CPU-bound. 80% gives correct headroom before scaling kicks in.
soul-demo now runs as a k3s Deployment with HPA (1–8 replicas, 60% CPU
target) instead of a bare background process. k3s starts first in
entrypoint.sh, imports the soul-demo:local OCI tar from
/var/lib/rancher/k3s/agent/images, and auto-applies the Deployment,
NodePort Service, and HPA from the server/manifests dir. neuron-web
starts only after the soul-demo pod is Running. Cloud Run gen2 execution
environment required for k3s (provides /dev/kmsg and Linux capabilities).
Root cause: the staged el_runtime.c (from el.git) wraps the entire OTLP
observability section (emit_metric, emit_log, trace_span_start/end) in
#ifdef HAVE_CURL. Without -DHAVE_CURL, those symbols are compiled out,
causing the undefined reference linker errors.
libcurl IS available (installed via libcurl4-openssl-dev), so -DHAVE_CURL
correctly enables the OTLP code path.
Also reverts the soul-demo compile to use el_runtime.o (the pre-compiled
cached object) now that the object will contain the correct symbols.
soul-demo.c was previously an older compiled artifact that triggered an
undefined reference to emit_metric/emit_log/trace_span_* at link time in CI.
Two fixes:
1. Rebuild soul-demo.c from soul-demo.el using current elc — cleaner
codegen (no double-wrapped el_from_float), same logic, unix_timestamp
collision with el_runtime.c removed.
2. Dockerfile.stage: compile soul-demo against el_runtime.c directly
(not el_runtime.o) so all runtime symbols are always resolved from the
staged source, bypassing any Docker layer cache divergence on el_runtime.o.
elb compiles each .el source independently (per-file codegen),
avoiding the exponential memory growth from concatenating all sources
into main-combined.el and feeding it to elc in one shot.
- dev.yaml: replace build-stage.sh with elb build + per-file JS elc
- Dockerfile.stage: COPY dist/neuron-landing (elb binary) directly
instead of compiling from pre-generated main.c. soul-demo stays as
cc compilation (small file, no risk).
Recovers original JS from git history and ports it into proper El source
files under src/js/. Each file wraps the original JS in a native_js call
inside a main() function, making it valid El that compiles to a
self-contained IIFE via elc --target=js --bundle.
Files added:
src/js/account-auth.el - Supabase OTP magic-link (sendMagicLink)
src/js/account-dashboard.el - Account dashboard: session, plan card, family
src/js/chat-widget.el - Demo chat widget (neuronDemoToggle/Send/Reset)
src/js/checkout-auth.el - Checkout auth: OAuth, email sign-in/up
src/js/checkout-free.el - Free plan: auth-badge watch -> payment reveal
src/js/checkout-stripe.el - Stripe Payment Element (reads NEURON_CFG)
src/js/enterprise.el - Enterprise inquiry form + headcount filter
src/js/environmental.el - Efficiency calculator slider
src/js/gallery.el - Gallery nav, search/sort, Supabase voting
src/js/main.el - Share page voting + copyForPlatform
src/js/marketplace.el - Developer interest form
src/js/nav.el - Nav hamburger + Mission dropdown
src/js/styles.el - Landing: nav scroll, reveal, founding counter
- deploy.yaml: restore elc from GCS (gs://neuron-ci-cache) keyed on
source SHA; only compile on cache miss, then upload for future runs
- Dockerfile.stage: pre-compile el_runtime.o as its own layer so the
expensive object is cached when only main.c changes between runs
- build-stage.sh: add --cache-from/--cache-to pointing at Artifact
Registry so apt-get + compilation layers survive across cold builds
Build pipeline
- build-stage.sh replaces the old in-Dockerfile bootstrap.py path. Host
pre-compiles src/*.el into dist/main.c via the canonical native elc at
foundation/el/dist/platform/elc and applies the stub-decl sed before
docker buildx runs.
- Dockerfile.stage drops bootstrap.py + python3 from the builder stage
and just runs cc on the host-supplied dist/main.c.
- Pre-rendered HTML shells under /srv/landing/ are now chowned to the
landing user so the El page-builder's fs_write at startup can rewrite
them — without that, post-COPY edits never reach the served HTML and
the served page stays as the stale build-time fallback.
Chat restore
- session.verified + session.verifiedAt persist through localStorage so
a return visit within 24h skips the Turnstile gate and lands directly
in the restored conversation.
- restoreOrGreet() is the single source of truth for what shows up in
the message pane after the gate clears: replays prior messages with
skipSave, else drops the canned hello once and remembers it.
- applyVerifiedDom() hides the gate / reveals the chat row, called both
from the verified-on-load path (DOMContentLoaded if loading, else
immediate) and from the Turnstile callback.
- neuronDemoReset clears verified + verifiedAt so the gate returns next
open.
Extracted JS assets (src/assets/js/*.js + manifest.json) and the
extract-js.py helper land here too — they match what the new build-stage
flow produces and removes the inline <script> blobs from the served HTML.
build-local.sh is no longer needed - bootstrap.py resolves imports
natively now. Dockerfile.stage already runs bootstrap.py on
dist/main-combined.el; the next image rev will switch to running
it directly on src/main.el.
Also: COPY src/llms.txt + the four prerendered HTML shells (about /
terms / enterprise-terms / index) into /srv/landing. The El handler
does fs_read(src_dir + "/llms.txt") which returned empty because the
file didn't exist in the container.
The /account "Loading..." spinner stayed on forever because the
browser-side waitlist read went through the anon key and didn't reach
the row. Replaced it with a POST /api/my-plan: the server verifies
the user's access_token via Supabase /auth/v1/user, then reads the
waitlist row with the service key. Bypasses RLS without exposing the
service key to the browser.
Stripped implementation comments from the served JS so the browser
doesn't broadcast how internals are shaped.
Build pipeline: declared the supabase_auth_user stub for both
build-local.sh and Dockerfile.stage so the bootstrap-injected forward
declarations match what's actually linked.
The auth-first flow blocked Stripe Elements from initialising for any
visitor without an existing Supabase session. Users hit the checkout
page, saw "Sign in to continue", and could not get to a card field at
all. Restored the inline-JS path (HEAD before extraction broke it),
flipped payment-section visible by default, kept the sign-in panel
behind an explicit "Already have an account? Sign in" link.
Build pipeline: added supabase_get stub injection and -lssl/-lcrypto
linker flags (web_stubs.c uses EVP for the AES-256-GCM transport).
Without those the Docker build aborts at link time.