Commit Graph

228 Commits

Author SHA1 Message Date
will.anderson acca3cfddf Merge pull request 'add unsafe-eval to CSP (El native_js compatibility)' (#88) from fix/stage-ci-paths into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 3m15s
Merge fix/stage-ci-paths into dev
2026-05-11 16:44:54 +00:00
will.anderson 90f7c3655e add unsafe-eval to CSP for El runtime native_js() compatibility
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 3m9s
El's native_js() compiles to eval(). checkout-auth.el uses native_js()
to embed the auth logic, so all window globals (showSignIn, initStripe,
etc.) live inside an eval call. Stage CSP was blocking it, leaving the
page with no auth functions defined.
2026-05-11 11:40:05 -05:00
will.anderson 6d3c7e2bcd Merge pull request 'remove --obfuscate from elc JS compile step' (#86) from fix/stage-ci-paths into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m54s
2026-05-11 16:11:33 +00:00
will.anderson 637b05af98 remove --obfuscate from elc JS compile step
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 2m8s
Stage CSP blocks 'unsafe-eval' which javascript-obfuscator introduces.
checkout-auth.js IIFE was crashing before assigning window globals,
causing all checkout E2E tests to fail.
2026-05-11 11:11:11 -05:00
will.anderson d90e8d1668 Merge pull request 'Fix Stripe CDN mock override and free-plan sync guards in E2E tests' (#84) from fix/stage-ci-paths into dev
Fix Stripe CDN mock override and free-plan sync guards in E2E tests
2026-05-11 15:36:21 +00:00
will.anderson c6fd06b3de Fix Stripe CDN mock override and free-plan sync guards in E2E tests
- Block real Stripe CDN (js.stripe.com) in injectMockStripe() so the
  addInitScript mock is never overwritten by the async-loaded SDK
- Replace waitForFunction(signUpWithEmail) with waitForLoadState in
  all 8 free-plan auth tests; defer scripts run before DOMContentLoaded
  so the function is guaranteed present without polling for it
2026-05-11 09:54:55 -05:00
will.anderson 91ecdaf3a5 Merge pull request 'Fix CI JS corruption from obfuscator stdout; clean up flaky test guards' (#81) from fix/stage-ci-paths into dev
Dev — Build & local smoke test / build-smoke (push) Failing after 1m53s
Merge fix/stage-ci-paths: fix CI JS corruption + flaky test guards
2026-05-11 14:16:29 +00:00
will.anderson 61f006f62d Fix CI JS corruption from obfuscator stdout; clean up flaky test guards
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m54s
- Strip [javascript-obfuscator-cli] progress line from elc --obfuscate
  output before writing to dist/js/ (was prepended to every compiled JS
  file, causing browser parse errors on stage)
- Remove spurious waitForFunction(signUpWithEmail) guards from
  buyer-name and buyer-email structural tests (pure DOM tests, no auth)
- Switch chat.spec.ts beforeEach to domcontentloaded (SSR elements
  present at DOM ready; networkidle caused cold-start timeouts)
2026-05-11 08:19:30 -05:00
will.anderson 48ba7716b8 Merge pull request 'Free plan Stripe age verification + soul demo personalization' (#79) from fix/stage-ci-paths into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m25s
Merge free plan age verification + soul demo personalization into dev
2026-05-11 07:05:35 +00:00
will.anderson c966f2b455 implement free plan age verification via Stripe SetupIntent; personalize soul demo greeting with user name and timezone
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m49s
2026-05-11 02:03:39 -05:00
will.anderson 74e84da41a Merge pull request 'Add tests/** to stage CI paths filter' (#77) from fix/stage-ci-paths into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m26s
2026-05-11 06:21:04 +00:00
will.anderson ac2d00d653 Add tests/** + playwright.config.ts to stage CI paths filter
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m52s
2026-05-11 01:20:43 -05: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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 5e1344af42 Merge pull request 'Fix soul-demo Docker build: --no-cache to avoid corrupted overlay2 layers' (#41) from fix/stage-source-check into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 4m42s
Fix soul-demo Docker build: --no-cache to avoid corrupted overlay2 layers
2026-05-10 15:57:13 +00:00
will.anderson d8acb126f5 Fix soul-demo Docker build: --no-cache to avoid corrupted overlay2 layers
Dev — Build & local smoke test / build-smoke (pull_request) Failing after 27s
2026-05-10 10:56:44 -05:00
will.anderson 87ac67a70e Merge pull request 'Selective Docker prune (preserve build cache) + k3s retry' (#39) from fix/stage-source-check into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 4m10s
2026-05-10 02:22:08 +00:00
will.anderson f838e0c8a7 Selective Docker prune to preserve build cache; retry k3s download
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 4m2s
2026-05-09 21:21:52 -05:00
will.anderson e520ba98ca Merge pull request 'Make docker prune non-fatal (concurrent prune race)' (#38) from fix/stage-source-check into dev
Dev — Build & local smoke test / build-smoke (push) Failing after 14m45s
2026-05-10 01:57:30 +00:00