Files
worldmonitor/Dockerfile.seed-bundle-resilience-validation
Elie Habib 8cc8781b19 fix(resilience): unbreak validation cron — Node 22 tsx loader path (#3041)
Two bugs compounded to crash the validation bundle since creation:

1. tsx resolution ambiguity: installing root devDeps + scripts deps put
   tsx at both /app/node_modules and /app/scripts/node_modules. Node 22
   picked the wrong one for --import tsx/esm — reproducible with an
   incomplete dist/ in one of the two installs.

2. tsx/esm triggers ERR_REQUIRE_CYCLE_MODULE on Node 22 when the loaded
   .ts graph has its own imports. tsx's esm-only loader uses require()
   internally and Node 22's stricter require-of-esm cycle detection
   rejects it. Reproduced locally with node-v22.22.2.

Fix: install only tsx at /app/node_modules (single-package npm install,
no package.json), use absolute path to tsx's default loader
(dist/loader.mjs, not dist/esm/index.mjs), and ship only the scripts
the bundle actually touches — including _proxy-utils.cjs which
_seed-utils.mjs top-level-requires.

Verified end-to-end against Node 22.22.2: all three validation scripts
(External-Benchmark, Outcome-Backtest, Sensitivity-Suite) run to
completion with no module or cycle errors.
2026-04-13 08:05:59 +04:00

50 lines
2.6 KiB
Docker

# =============================================================================
# Seed Bundle: Resilience Validation (weekly cron)
# =============================================================================
# Runs scripts/seed-bundle-resilience-validation.mjs which spawns:
# - benchmark-resilience-external.mjs
# - backtest-resilience-outcomes.mjs
# - validate-resilience-sensitivity.mjs (imports ../server/*.ts via tsx)
#
# The validation scripts only touch node: builtins, local helpers, and .ts
# files under ../server/ via dynamic import. The ONLY external runtime dep
# is tsx (to register the ESM loader for .ts imports).
# =============================================================================
FROM node:22-alpine
WORKDIR /app
# Install only tsx at /app/node_modules/tsx. Single-package install keeps the
# image small and eliminates the previous ambiguity where tsx existed at both
# /app/node_modules and /app/scripts/node_modules and Node 22 resolved the
# wrong one (which had an incomplete dist/). The test is a build-time assert
# that tsx's ESM loader is at the path referenced by NODE_OPTIONS below.
RUN npm install --prefix /app --no-save --no-audit --no-fund --no-package-lock tsx@4.21.0 \
&& test -f /app/node_modules/tsx/dist/loader.mjs
# Copy only the scripts the bundle actually runs + their local helpers.
# _seed-utils.mjs eagerly createRequire()s _proxy-utils.cjs at module load,
# so the .cjs helper must ship alongside even if unused by these validators.
# validate-resilience-sensitivity.mjs dynamic-imports ../server/*.ts so the
# full server/ tree is copied (scorers pull from shared/ and data/ at runtime).
COPY scripts/_seed-utils.mjs scripts/_bundle-runner.mjs scripts/_proxy-utils.cjs ./scripts/
COPY scripts/seed-bundle-resilience-validation.mjs ./scripts/
COPY scripts/benchmark-resilience-external.mjs ./scripts/
COPY scripts/backtest-resilience-outcomes.mjs ./scripts/
COPY scripts/validate-resilience-sensitivity.mjs ./scripts/
COPY server/ ./server/
COPY shared/ ./shared/
COPY data/ ./data/
COPY tsconfig.json tsconfig.api.json ./
# Absolute path sidesteps bare-specifier resolution entirely — Node 22 can
# resolve "tsx/esm" to the wrong node_modules when multiple exist, and "tsx/esm"
# also triggers ERR_REQUIRE_CYCLE_MODULE under Node 22 when the loaded .ts
# has its own imports. dist/loader.mjs is tsx's default ESM loader and works
# for both the parent process and children spawned via execFile (which inherit
# NODE_OPTIONS).
ENV NODE_OPTIONS="--max-old-space-size=8192 --dns-result-order=ipv4first --import=file:///app/node_modules/tsx/dist/loader.mjs"
CMD ["node", "scripts/seed-bundle-resilience-validation.mjs"]