Skip to content

Merge pull request #385 from dotunv/feat/319-monte-carlo-simulation #376

Merge pull request #385 from dotunv/feat/319-monte-carlo-simulation

Merge pull request #385 from dotunv/feat/319-monte-carlo-simulation #376

Workflow file for this run

name: Node CI
on:
push:
branches:
- main
- develop
pull_request:
types: [opened, synchronize, reopened]
jobs:
ci:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14.4
env:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: db
ports:
- 5432:5432
options: >-
--health-cmd="pg_isready -U user"
--health-interval=10s
--health-timeout=5s
--health-retries=5
env:
NODE_ENV: test
DATABASE_URL: postgresql://user:pass@localhost:5432/db
WALLET_ENCRYPTION_KEY: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
# Non-secret stubs required by env.ts at module-load time
STELLAR_NETWORK: testnet
STELLAR_RPC_URL: https://rpc.example.com
STELLAR_AGENT_SECRET_KEY: SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
VAULT_CONTRACT_ID: CDUMMYVAULTCONTRACTID
USDC_TOKEN_ADDRESS: CDUMMYUSDC
ANTHROPIC_API_KEY: sk-ant-smoke-key
JWT_SEED: smoke-jwt-seed-0123456789abcdef0123456789
JWT_SESSION_TTL_HOURS: '24'
JWT_NONCE_TTL_MS: '300000'
JWT_CLEANUP_INTERVAL_MS: '86400000'
TWILIO_AUTH_TOKEN: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
INTERNAL_SERVICE_TOKEN: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
TRUSTED_IPS: 127.0.0.1
CORS_ORIGINS: '*'
HTTP_CLIENT_TIMEOUT_MS: '1000'
steps:
# Checkout repository
- name: Checkout repository
uses: actions/checkout@v4
# Setup Node.js
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
# Install dependencies
- name: Install dependencies
run: npm ci
- name: Prisma generate
run: npx prisma generate
- name: Prisma migrate deploy
run: npx prisma migrate deploy
# Run TypeScript lint check
- name: Run lint
run: npm run lint
- name: Run format check
run: npm run format:check
# Build the project
- name: Build project
run: npm run build
# Run tests
- name: Run tests
run: npm test
# ── Security scanning ─────────────────────────────────────────────────────
# Issue #114: npm audit for known vulnerabilities
# Fails on high or critical CVEs to prevent merging vulnerable dependencies
#
# Policy: Builds fail on HIGH or CRITICAL vulnerabilities
# - HIGH/CRITICAL: Must be fixed before merge (blocking)
# - MODERATE: Review required, fix in follow-up PR (non-blocking via Dependabot)
# - LOW: Tracked via Dependabot, fix during regular maintenance
#
# Dependabot automatically creates PRs for vulnerable dependencies
security-scan:
name: Security audit
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run npm audit (HIGH and CRITICAL only)
run: npm audit --audit-level=high
continue-on-error: false
- name: License check (block GPL/AGPL/LGPL)
# Prevents copyleft licences from entering the dependency tree.
# Allowed: MIT, ISC, Apache-2.0, BSD-2-Clause, BSD-3-Clause, 0BSD, BlueOak-1.0.0, CC0-1.0,
# Python-2.0 (PSF — permissive, transitive argparse@2),
# CC-BY-4.0 (attribution-only, transitive caniuse-lite data),
# Unlicense (public-domain dedication, transitive fast-sha256)
run: |
npx license-checker --onlyAllow \
'MIT;ISC;Apache-2.0;BSD-2-Clause;BSD-3-Clause;0BSD;BlueOak-1.0.0;CC0-1.0;Python-2.0;CC-BY-4.0;Unlicense' \
--excludePrivatePackages
# ── Issue #100: migration smoke gate ──────────────────────────────────────
# Isolated job that re-applies migrations against a clean DB and runs the
# smoke test. Fails the pipeline if either step errors, preventing a broken
# migration from reaching production.
migration-smoke:
name: Migration smoke test
runs-on: ubuntu-latest
needs: ci
services:
postgres:
image: postgres:14.4
env:
POSTGRES_USER: smoke_user
POSTGRES_PASSWORD: smoke_pass
POSTGRES_DB: smoke_db
ports:
- 5433:5432
options: >-
--health-cmd="pg_isready -U smoke_user"
--health-interval=10s
--health-timeout=5s
--health-retries=5
env:
NODE_ENV: test
# scripts/smoke-health.sh probes port 3001 — the server must listen there.
PORT: '3001'
DATABASE_URL: postgresql://smoke_user:smoke_pass@localhost:5433/smoke_db
# Non-secret stubs required by env.ts at module-load time
STELLAR_NETWORK: testnet
# This job boots the real server; startEventListener() calls
# getLatestLedger() with no client timeout before app.listen(), so the
# endpoint must resolve or startup stalls.
STELLAR_RPC_URL: https://soroban-testnet.stellar.org
STELLAR_AGENT_SECRET_KEY: SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
VAULT_CONTRACT_ID: CDUMMYVAULTCONTRACTID
USDC_TOKEN_ADDRESS: CDUMMYUSDC
ANTHROPIC_API_KEY: sk-ant-smoke-key
JWT_SEED: smoke-jwt-seed-0123456789abcdef0123456789
JWT_SESSION_TTL_HOURS: '24'
JWT_NONCE_TTL_MS: '300000'
JWT_CLEANUP_INTERVAL_MS: '86400000'
WALLET_ENCRYPTION_KEY: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
# Required by env.ts and asserted again by initServices().
TWILIO_AUTH_TOKEN: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Prisma generate
run: npx prisma generate
# Informational only: on a fresh smoke database every migration is
# pending, and `migrate status` exits non-zero whenever that is true.
# The real gate is "Confirm no pending migrations after deploy" below.
- name: Check migration status (pre-deploy, informational)
run: npx prisma migrate status || true
- name: Apply migrations to smoke DB
run: npx prisma migrate deploy
- name: Confirm no pending migrations after deploy
run: |
npx prisma migrate status | tee /tmp/migrate-status.txt
if grep -q "following migration have not yet been applied" /tmp/migrate-status.txt; then
echo "::error::Pending migrations remain after migrate deploy"
exit 1
fi
# smoke-health.sh runs the compiled server, so the artifact must exist.
- name: Build project
run: npm run build
- name: Run smoke test
run: npm run smoke
# ── Issue #195: Docs and deployment manifests validation ───────────────────
# Validates Markdown links, code fences, and Kubernetes manifests to catch
# broken examples, stale commands, and malformed manifests before they reach operators.
docs-validation:
name: Docs and manifests validation
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install markdown-link-check
run: npm install -g markdown-link-check
- name: Check Markdown links in docs
run: |
# Check internal links only (ignore external links that may be flaky)
for file in docs/*.md; do
if [ -f "$file" ]; then
echo "Checking links in $file"
markdown-link-check --config .markdown-link-check.json "$file" || {
echo "::error file=$file::Markdown link check failed"
exit 1
}
fi
done
- name: Validate code fences in docs
run: |
# Check for unclosed code fences in markdown files
for file in docs/*.md; do
if [ -f "$file" ]; then
# Count opening and closing code fences
open=$(grep -c '^```' "$file" || true)
if [ $((open % 2)) -ne 0 ]; then
echo "::error file=$file::Unclosed code fence detected"
exit 1
fi
fi
done
# Kubernetes manifest validation lives in k8s-validate.yml (kubeconform,
# fully offline) and triggers whenever deploy/k8s changes. kubectl's
# client dry-run needs a live cluster for API discovery, so it can never
# pass on a bare runner and was removed from this job.
- name: Check referenced file paths in docs
run: |
# Extract file paths from markdown code blocks and check if they exist
for file in docs/*.md; do
if [ -f "$file" ]; then
# Extract paths like `src/config/env.ts` or `docs/OBSERVABILITY.md`
grep -oE '\`[a-zA-Z0-9_./-]+\.[a-z]+\`' "$file" | sed 's/`//g' | while read path; do
if [ -n "$path" ] && [ ! -f "$path" ] && [ ! -d "$path" ]; then
# Only warn for likely file references (contains .)
if [[ "$path" == *.* ]]; then
echo "::warning file=$file::Referenced file may not exist: $path"
fi
fi
done
fi
done
- name: Check command examples in docs
run: |
# Check for common command patterns that might be outdated
for file in docs/*.md; do
if [ -f "$file" ]; then
# Check for npm scripts that don't exist in package.json
grep -oE 'npm (run|test) [a-z0-9_-]+' "$file" | while read cmd; do
script=$(echo "$cmd" | awk '{print $NF}')
if ! grep -q "\"$script\"" package.json; then
echo "::warning file=$file::npm script may not exist: $script"
fi
done
fi
done