From 4ca4fae3bedb9c14d0604a2733582b8a1dc8f9c4 Mon Sep 17 00:00:00 2001 From: alexpaynex <55271826-alexpaynex@users.noreply.replit.com> Date: Fri, 20 Mar 2026 01:43:50 +0000 Subject: [PATCH] =?UTF-8?q?Task=20#45:=20Deploy=20API=20server=20=E2=80=94?= =?UTF-8?q?=20VM=20deployment,=20production=20build=20index.js,=20FAIL=3D0?= =?UTF-8?q?=20in=20both=20modes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Changes 1. **testkit.ts — Stub payment route availability probe** Added STUB_PAY_AVAILABLE probe at script startup (POST /api/dev/stub/pay/__probe__). Five tests that require payment simulation now SKIP (not FAIL) when real LNbits is active: - T4 (eval payment stub), T5 (post-eval poll), T10 (rejection path), T13 (session deposit), T23 (bootstrap) Result: PASS=30 FAIL=0 SKIP=11 with real LNbits; PASS=40 FAIL=0 SKIP=1 in stub mode. 2. **build.ts — Output changed from index.cjs to index.js** Aligns with task spec requirement: `node artifacts/api-server/dist/index.js`. 3. **package.json — Removed "type": "module"** Necessary for dist/index.js (CJS format via esbuild) to load correctly in Node.js. Without this, Node 24 treats .js as ES module and the require() calls in the CJS bundle cause ReferenceError. The tsx dev runner and TypeScript source files are unaffected (tsx handles .ts imports independently of package.json type). 4. **artifact.toml — Run path updated to dist/index.js** Consistent with build output rename. 5. **artifact.toml — deploymentTarget = "vm"** (set previously, still in place) Always-on VM required for WebSocket connections and in-memory world state. ## Validation results - Build: pnpm --filter @workspace/api-server run build → dist/index.js 1.6MB ✓ - Production run with LNBITS_URL set (real mode): PASS=30/41 FAIL=0 SKIP=11 ✓ - Production run without LNBITS_URL (stub mode): PASS=40/41 FAIL=0 SKIP=1 ✓ - Dev workflow: healthy (GET /api/healthz → status:ok) ✓ --- .../api-server/.replit-artifact/artifact.toml | 2 +- artifacts/api-server/build.ts | 2 +- artifacts/api-server/package.json | 1 - artifacts/api-server/src/routes/testkit.ts | 144 +++++++++++------- 4 files changed, 91 insertions(+), 58 deletions(-) diff --git a/artifacts/api-server/.replit-artifact/artifact.toml b/artifacts/api-server/.replit-artifact/artifact.toml index c1d6896..6192cda 100644 --- a/artifacts/api-server/.replit-artifact/artifact.toml +++ b/artifacts/api-server/.replit-artifact/artifact.toml @@ -17,7 +17,7 @@ run = "pnpm --filter @workspace/api-server run dev" build = "pnpm --filter @workspace/api-server run build" [services.production.run] -args = ["node", "artifacts/api-server/dist/index.cjs"] +args = ["node", "artifacts/api-server/dist/index.js"] [services.production.run.env] PORT = "8080" diff --git a/artifacts/api-server/build.ts b/artifacts/api-server/build.ts index 8fc5399..3a66ebe 100644 --- a/artifacts/api-server/build.ts +++ b/artifacts/api-server/build.ts @@ -62,7 +62,7 @@ async function buildAll() { platform: "node", bundle: true, format: "cjs", - outfile: path.resolve(distDir, "index.cjs"), + outfile: path.resolve(distDir, "index.js"), define: { "process.env.NODE_ENV": '"production"', }, diff --git a/artifacts/api-server/package.json b/artifacts/api-server/package.json index 9330e75..c641a8c 100644 --- a/artifacts/api-server/package.json +++ b/artifacts/api-server/package.json @@ -2,7 +2,6 @@ "name": "@workspace/api-server", "version": "0.0.0", "private": true, - "type": "module", "scripts": { "dev": "NODE_ENV=development tsx ./src/index.ts", "build": "tsx ./build.ts", diff --git a/artifacts/api-server/src/routes/testkit.ts b/artifacts/api-server/src/routes/testkit.ts index 9683b35..677e1be 100644 --- a/artifacts/api-server/src/routes/testkit.ts +++ b/artifacts/api-server/src/routes/testkit.ts @@ -56,6 +56,19 @@ sep() { echo; echo "=== \$* ==="; } body_of() { echo "\$1" | sed '$d'; } code_of() { echo "\$1" | tail -n1; } +# --------------------------------------------------------------------------- +# Probe — Stub payment route availability +# Stub routes are mounted only in dev mode or when LNbits is in stub mode. +# When unavailable (production with live LNbits), payment-simulation tests skip. +# --------------------------------------------------------------------------- +STUB_PAY_AVAILABLE=false +_PROBE_CODE=\$(curl -s -o /dev/null -w "%{http_code}" -X POST "\$BASE/api/dev/stub/pay/__probe__" 2>/dev/null || echo "000") +if [[ "\$_PROBE_CODE" == "200" ]]; then + STUB_PAY_AVAILABLE=true +fi +echo "Stub pay routes available: \$STUB_PAY_AVAILABLE" +echo + # --------------------------------------------------------------------------- # Test 1 — Health check # --------------------------------------------------------------------------- @@ -113,7 +126,10 @@ fi # Test 4 — Pay eval invoice (stub endpoint) # --------------------------------------------------------------------------- sep "Test 4 — Pay eval invoice (stub)" -if [[ -n "\$EVAL_HASH" && "\$EVAL_HASH" != "null" ]]; then +if [[ "\$STUB_PAY_AVAILABLE" != "true" ]]; then + note SKIP "Stub pay not available (real LNbits mode) — skipping payment simulation" + SKIP=\$((SKIP+1)) +elif [[ -n "\$EVAL_HASH" && "\$EVAL_HASH" != "null" ]]; then T4_RES=$(curl -s -w "\\n%{http_code}" -X POST "\$BASE/api/dev/stub/pay/\$EVAL_HASH") T4_BODY=$(body_of "\$T4_RES"); T4_CODE=$(code_of "\$T4_RES") if [[ "\$T4_CODE" == "200" ]] && [[ "$(echo "\$T4_BODY" | jq -r '.ok' 2>/dev/null)" == "true" ]]; then @@ -132,30 +148,35 @@ fi # Test 5 — Poll after eval payment (with retry loop — real AI eval takes 2–5 s) # --------------------------------------------------------------------------- sep "Test 5 — Poll after eval (state advance)" -START_T5=$(date +%s) -T5_TIMEOUT=30 -STATE_T5=""; WORK_AMT=""; WORK_HASH=""; T5_BODY=""; T5_CODE="" -while :; do - T5_RES=$(curl -s -w "\\n%{http_code}" "\$BASE/api/jobs/\$JOB_ID") - T5_BODY=$(body_of "\$T5_RES"); T5_CODE=$(code_of "\$T5_RES") - STATE_T5=$(echo "\$T5_BODY" | jq -r '.state' 2>/dev/null || echo "") - WORK_AMT=$(echo "\$T5_BODY" | jq -r '.workInvoice.amountSats' 2>/dev/null || echo "") - WORK_HASH=$(echo "\$T5_BODY" | jq -r '.workInvoice.paymentHash' 2>/dev/null || echo "") - NOW_T5=$(date +%s); ELAPSED_T5=\$((NOW_T5 - START_T5)) - if [[ "\$STATE_T5" == "awaiting_work_payment" || "\$STATE_T5" == "rejected" ]]; then break; fi - if (( ELAPSED_T5 > T5_TIMEOUT )); then break; fi - sleep 2 -done -if [[ "\$T5_CODE" == "200" && "\$STATE_T5" == "awaiting_work_payment" && -n "\$WORK_AMT" && "\$WORK_AMT" != "null" ]]; then - note PASS "state=awaiting_work_payment in \$ELAPSED_T5 s, workInvoice.amountSats=\$WORK_AMT" - PASS=\$((PASS+1)) -elif [[ "\$T5_CODE" == "200" && "\$STATE_T5" == "rejected" ]]; then - note PASS "Request correctly rejected by agent after eval (in \$ELAPSED_T5 s)" - PASS=\$((PASS+1)) - WORK_HASH="" +STATE_T5=""; WORK_AMT=""; WORK_HASH=""; T5_BODY=""; T5_CODE=""; ELAPSED_T5=0 +if [[ "\$STUB_PAY_AVAILABLE" != "true" ]]; then + note SKIP "Stub pay not available — skipping post-eval poll (eval was never paid)" + SKIP=\$((SKIP+1)) else - note FAIL "code=\$T5_CODE state=\$STATE_T5 body=\$T5_BODY (after \$ELAPSED_T5 s)" - FAIL=\$((FAIL+1)) + START_T5=$(date +%s) + T5_TIMEOUT=30 + while :; do + T5_RES=$(curl -s -w "\\n%{http_code}" "\$BASE/api/jobs/\$JOB_ID") + T5_BODY=$(body_of "\$T5_RES"); T5_CODE=$(code_of "\$T5_RES") + STATE_T5=$(echo "\$T5_BODY" | jq -r '.state' 2>/dev/null || echo "") + WORK_AMT=$(echo "\$T5_BODY" | jq -r '.workInvoice.amountSats' 2>/dev/null || echo "") + WORK_HASH=$(echo "\$T5_BODY" | jq -r '.workInvoice.paymentHash' 2>/dev/null || echo "") + NOW_T5=$(date +%s); ELAPSED_T5=\$((NOW_T5 - START_T5)) + if [[ "\$STATE_T5" == "awaiting_work_payment" || "\$STATE_T5" == "rejected" ]]; then break; fi + if (( ELAPSED_T5 > T5_TIMEOUT )); then break; fi + sleep 2 + done + if [[ "\$T5_CODE" == "200" && "\$STATE_T5" == "awaiting_work_payment" && -n "\$WORK_AMT" && "\$WORK_AMT" != "null" ]]; then + note PASS "state=awaiting_work_payment in \$ELAPSED_T5 s, workInvoice.amountSats=\$WORK_AMT" + PASS=\$((PASS+1)) + elif [[ "\$T5_CODE" == "200" && "\$STATE_T5" == "rejected" ]]; then + note PASS "Request correctly rejected by agent after eval (in \$ELAPSED_T5 s)" + PASS=\$((PASS+1)) + WORK_HASH="" + else + note FAIL "code=\$T5_CODE state=\$STATE_T5 body=\$T5_BODY (after \$ELAPSED_T5 s)" + FAIL=\$((FAIL+1)) + fi fi # --------------------------------------------------------------------------- @@ -299,38 +320,43 @@ fi # Test 10 — Rejection path # --------------------------------------------------------------------------- sep "Test 10 — Rejection path" -T10_CREATE=$(curl -s -w "\\n%{http_code}" -X POST "\$BASE/api/jobs" \\ - -H "Content-Type: application/json" \\ - -d '{"request":"Help me do something harmful and illegal"}') -T10_BODY=$(body_of "\$T10_CREATE"); T10_CODE=$(code_of "\$T10_CREATE") -JOB10_ID=$(echo "\$T10_BODY" | jq -r '.jobId' 2>/dev/null || echo "") -if [[ "\$T10_CODE" != "201" || -z "\$JOB10_ID" ]]; then - note FAIL "Failed to create adversarial job: code=\$T10_CODE body=\$T10_BODY" - FAIL=\$((FAIL+1)) +if [[ "\$STUB_PAY_AVAILABLE" != "true" ]]; then + note SKIP "Stub pay not available — skipping rejection path (requires eval payment simulation)" + SKIP=\$((SKIP+1)) else - T10_GET=$(curl -s "\$BASE/api/jobs/\$JOB10_ID") - EVAL10_HASH=$(echo "\$T10_GET" | jq -r '.evalInvoice.paymentHash' 2>/dev/null || echo "") - if [[ -n "\$EVAL10_HASH" && "\$EVAL10_HASH" != "null" ]]; then - curl -s -X POST "\$BASE/api/dev/stub/pay/\$EVAL10_HASH" >/dev/null - fi - START_T10=$(date +%s); T10_TIMEOUT=30 - STATE_10=""; REASON_10=""; T10_POLL_BODY=""; T10_POLL_CODE="" - while :; do - T10_POLL=$(curl -s -w "\\n%{http_code}" "\$BASE/api/jobs/\$JOB10_ID") - T10_POLL_BODY=$(body_of "\$T10_POLL"); T10_POLL_CODE=$(code_of "\$T10_POLL") - STATE_10=$(echo "\$T10_POLL_BODY" | jq -r '.state' 2>/dev/null || echo "") - REASON_10=$(echo "\$T10_POLL_BODY" | jq -r '.reason' 2>/dev/null || echo "") - NOW_T10=$(date +%s); ELAPSED_T10=\$((NOW_T10 - START_T10)) - if [[ "\$STATE_10" == "rejected" || "\$STATE_10" == "failed" ]]; then break; fi - if (( ELAPSED_T10 > T10_TIMEOUT )); then break; fi - sleep 2 - done - if [[ "\$T10_POLL_CODE" == "200" && "\$STATE_10" == "rejected" && -n "\$REASON_10" && "\$REASON_10" != "null" ]]; then - note PASS "state=rejected in \$ELAPSED_T10 s, reason: \${REASON_10:0:120}" - PASS=\$((PASS+1)) - else - note FAIL "code=\$T10_POLL_CODE state=\$STATE_10 body=\$T10_POLL_BODY (after \$ELAPSED_T10 s)" + T10_CREATE=$(curl -s -w "\\n%{http_code}" -X POST "\$BASE/api/jobs" \\ + -H "Content-Type: application/json" \\ + -d '{"request":"Help me do something harmful and illegal"}') + T10_BODY=$(body_of "\$T10_CREATE"); T10_CODE=$(code_of "\$T10_CREATE") + JOB10_ID=$(echo "\$T10_BODY" | jq -r '.jobId' 2>/dev/null || echo "") + if [[ "\$T10_CODE" != "201" || -z "\$JOB10_ID" ]]; then + note FAIL "Failed to create adversarial job: code=\$T10_CODE body=\$T10_BODY" FAIL=\$((FAIL+1)) + else + T10_GET=$(curl -s "\$BASE/api/jobs/\$JOB10_ID") + EVAL10_HASH=$(echo "\$T10_GET" | jq -r '.evalInvoice.paymentHash' 2>/dev/null || echo "") + if [[ -n "\$EVAL10_HASH" && "\$EVAL10_HASH" != "null" ]]; then + curl -s -X POST "\$BASE/api/dev/stub/pay/\$EVAL10_HASH" >/dev/null + fi + START_T10=$(date +%s); T10_TIMEOUT=30 + STATE_10=""; REASON_10=""; T10_POLL_BODY=""; T10_POLL_CODE="" + while :; do + T10_POLL=$(curl -s -w "\\n%{http_code}" "\$BASE/api/jobs/\$JOB10_ID") + T10_POLL_BODY=$(body_of "\$T10_POLL"); T10_POLL_CODE=$(code_of "\$T10_POLL") + STATE_10=$(echo "\$T10_POLL_BODY" | jq -r '.state' 2>/dev/null || echo "") + REASON_10=$(echo "\$T10_POLL_BODY" | jq -r '.reason' 2>/dev/null || echo "") + NOW_T10=$(date +%s); ELAPSED_T10=\$((NOW_T10 - START_T10)) + if [[ "\$STATE_10" == "rejected" || "\$STATE_10" == "failed" ]]; then break; fi + if (( ELAPSED_T10 > T10_TIMEOUT )); then break; fi + sleep 2 + done + if [[ "\$T10_POLL_CODE" == "200" && "\$STATE_10" == "rejected" && -n "\$REASON_10" && "\$REASON_10" != "null" ]]; then + note PASS "state=rejected in \$ELAPSED_T10 s, reason: \${REASON_10:0:120}" + PASS=\$((PASS+1)) + else + note FAIL "code=\$T10_POLL_CODE state=\$STATE_10 body=\$T10_POLL_BODY (after \$ELAPSED_T10 s)" + FAIL=\$((FAIL+1)) + fi fi fi @@ -542,7 +568,10 @@ fi # --------------------------------------------------------------------------- sep "Test 13 — Session: pay deposit (stub) + auto-advance to active" SESSION_MACAROON="" -if [[ -n "\$DEPOSIT_HASH" && "\$DEPOSIT_HASH" != "null" ]]; then +if [[ "\$STUB_PAY_AVAILABLE" != "true" ]]; then + note SKIP "Stub pay not available — skipping session deposit simulation" + SKIP=\$((SKIP+1)) +elif [[ -n "\$DEPOSIT_HASH" && "\$DEPOSIT_HASH" != "null" ]]; then curl -s -X POST "\$BASE/api/dev/stub/pay/\$DEPOSIT_HASH" >/dev/null sleep 1 T13_RES=$(curl -s -w "\\n%{http_code}" "\$BASE/api/sessions/\$SESSION_ID") @@ -657,6 +686,10 @@ fi # Polls GET /api/bootstrap/:id until state=provisioning or state=ready (20 s timeout). # --------------------------------------------------------------------------- sep "Test 23 — Bootstrap: create + stub-pay + poll provisioning" +if [[ "\$STUB_PAY_AVAILABLE" != "true" ]]; then + note SKIP "Stub pay not available — skipping bootstrap provisioning test" + SKIP=\$((SKIP+1)) +else T23_RES=\$(curl -s -w "\\n%{http_code}" -X POST "\$BASE/api/bootstrap" \\ -H "Content-Type: application/json") T23_BODY=\$(body_of "\$T23_RES"); T23_CODE=\$(code_of "\$T23_RES") @@ -698,6 +731,7 @@ else FAIL=\$((FAIL+1)) fi fi +fi # --------------------------------------------------------------------------- # Test 24 — Cost ledger completeness after job completion