Skip to content

hotfix(resilience): sovereign-status.json → scripts/shared/ (Railway bundle ENOENT, fixes #3435 follow-up)#3441

Merged
koala73 merged 1 commit into
mainfrom
hotfix/resilience-sovereign-status-bundle-path
Apr 26, 2026
Merged

hotfix(resilience): sovereign-status.json → scripts/shared/ (Railway bundle ENOENT, fixes #3435 follow-up)#3441
koala73 merged 1 commit into
mainfrom
hotfix/resilience-sovereign-status-bundle-path

Conversation

@koala73

@koala73 koala73 commented Apr 26, 2026

Copy link
Copy Markdown
Owner

Summary

Production hotfix. Railway seed-bundle-resilience container has been crashing on every cron tick since PR #3435 merged with:

Error: ENOENT: no such file or directory,
  open '/server/worldmonitor/resilience/v1/registries/sovereign-status.json'
    at file:///app/shared/rankable-universe.mjs:49:15

The resilience score-warming cron has been completely down. This PR moves sovereign-status.json into scripts/shared/ — the bundle-reachable location matching the established codebase pattern.

Root cause

Per memory worldmonitor-scripts-package-json-install-scope: Railway NIXPACKS services with rootDirectory=scripts/ only ship files under scripts/. PR #3435 placed the JSON at server/worldmonitor/resilience/v1/registries/sovereign-status.json — that resolved locally and on Vercel but the Railway bundle container can't reach above its scripts/ root.

What ships

  • Move: server/.../registries/sovereign-status.jsonscripts/shared/sovereign-status.json
  • Updated scripts/shared/rankable-universe.mjs — sibling read (no path traversal)
  • Updated server/worldmonitor/resilience/v1/_rankable-universe.ts — import from scripts/shared/ matching _dimension-scorers.ts pattern
  • Updated tests/resilience-universe-filter.test.mts — new path
  • Removed empty server/.../registries/ directory

Verification (4 layers)

  • npm run typecheck clean
  • 15/15 universe-filter tests pass
  • esbuild --bundle --platform=neutral produces clean bundle (Vercel-equivalent)
  • cd scripts && node -e "import('./shared/rankable-universe.mjs')" loads 196 entries (Railway bundle path simulation)

Test plan post-merge

  • Watch Railway seed-bundle-resilience logs for next cron tick — Finished in Xs, ran:1 skipped:0 deferred:0 failed:0
  • Confirm resilience:ranking:v15 Redis key updates (cron resumed)

… (Railway bundle ENOENT)

Production failure post-PR-#3435: Railway seed-bundle-resilience
container crashes on every cron tick with:

```
Error: ENOENT: no such file or directory,
  open '/server/worldmonitor/resilience/v1/registries/sovereign-status.json'
    at file:///app/shared/rankable-universe.mjs:49:15
[Bundle:resilience] Finished in 0.2s, ran:0 skipped:1 deferred:0 failed:1
```

**Root cause** (memory `worldmonitor-scripts-package-json-install-scope`):
Railway NIXPACKS services with `rootDirectory=scripts/` only ship files
under scripts/ into the container. PR #3435 placed sovereign-status.json
under server/worldmonitor/resilience/v1/registries/ — that resolved
locally and on Vercel (different bundlers, different file layouts) but
ENOENT'd at Railway runtime. Score-warming cron has been failing on
every tick since merge.

**Fix:** move JSON to `scripts/shared/sovereign-status.json` — the
canonical location for shared JSON consumed by both seeders AND
server-side code (alongside iso2-to-iso3.json, country-names.json
etc., per `_dimension-scorers.ts:1-2` import pattern).

**Updates:**
- `scripts/shared/rankable-universe.mjs`: sibling read,
  `resolve(here, 'sovereign-status.json')` — no path traversal
- `server/worldmonitor/resilience/v1/_rankable-universe.ts`:
  `import sovereignStatus from '../../../../scripts/shared/sovereign-status.json'`
  matching `_dimension-scorers.ts` pattern
- `tests/resilience-universe-filter.test.mts`: updated path
- `server/worldmonitor/resilience/v1/registries/`: removed (was
  intermediate location to avoid PR #3433's cohort shape gate; the
  scripts/shared/ location ALSO sits outside that gate's scope)

**Verification (4 layers):**
- `npm run typecheck` clean
- 15/15 universe-filter tests pass (full resilience suite would also
  pass; ran narrower subset since this is a path-only change)
- `npx esbuild --bundle --platform=neutral` produces clean bundle (Vercel)
- `cd scripts && node -e "import('./shared/rankable-universe.mjs')..."`
  loads 196 entries successfully (Railway bundle path simulation)

**Lesson** (now in skill `worldmonitor-scripts-package-json-install-scope`):
when a server-side TS module needs to import a JSON asset that is
ALSO consumed by a Railway NIXPACKS bundle service, the JSON MUST
live under `scripts/shared/`. server/.../whatever/ paths resolve
fine in tsc + tsx + Vercel/esbuild but ENOENT in the Railway container.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@vercel

vercel Bot commented Apr 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
worldmonitor Ready Ready Preview, Comment Apr 26, 2026 10:07pm

Request Review

@greptile-apps

greptile-apps Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This hotfix relocates sovereign-status.json from server/worldmonitor/resilience/v1/registries/ to scripts/shared/ so the Railway seed-bundle-resilience container (which only ships files under scripts/) can resolve it at runtime. All three consuming surfaces — the .mjs seeder module, the .ts server-side mirror, and the test file — are updated consistently.

Confidence Score: 4/5

Safe to merge — the fix is targeted and all path resolutions are verified; only a minor comment inaccuracy remains.

All four files make exactly the right mechanical change (rename + path updates). Relative path depths have been verified correct for both the .mjs sibling resolve and the .ts four-level-up import. The one P2 finding is a comment that misdescribes the cited precedent but does not affect runtime behaviour. No P0 or P1 issues found.

server/worldmonitor/resilience/v1/_rankable-universe.ts — minor comment inaccuracy about the cited import precedent.

Important Files Changed

Filename Overview
scripts/shared/rankable-universe.mjs Path traversal replaced with sibling resolve — correct and container-safe; updated comment explains Railway/Vercel context clearly.
scripts/shared/sovereign-status.json Pure rename from server/.../registries/ — no content changes; now within the Railway NIXPACKS bundle boundary.
server/worldmonitor/resilience/v1/_rankable-universe.ts Import path updated to ../../../../scripts/shared/sovereign-status.json — correct depth from server/worldmonitor/resilience/v1/; comment cites _dimension-scorers.ts as precedent but that file imports from ../../../../shared/ (repo-level), not scripts/shared/.
tests/resilience-universe-filter.test.mts SOVEREIGN_STATUS_PATH updated to ../scripts/shared/sovereign-status.json — correct relative path from tests/; all 15 test cases unchanged.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    JSON["scripts/shared/sovereign-status.json\n(196 entries: 193 UN members + 3 SARs)"]

    MJS["scripts/shared/rankable-universe.mjs\nfs.readFileSync — plain node runtime\n(Railway seeders)"]
    TS["server/worldmonitor/resilience/v1/_rankable-universe.ts\nES JSON import — Vercel esbuild bundler\n(Edge Function handlers)"]
    TEST["tests/resilience-universe-filter.test.mts\nreadFileSync — node:test runner"]

    SEEDER1["scripts/seed-resilience-static.mjs\nisInRankableUniverse()"]
    SEEDER2["scripts/seed-resilience-scores.mjs\nisInRankableUniverse()"]
    HANDLER["resilience ranking handler\n_rankable-universe.ts"]

    JSON --> MJS
    JSON --> TS
    JSON --> TEST

    MJS --> SEEDER1
    MJS --> SEEDER2
    TS --> HANDLER

    subgraph Railway["Railway container (rootDirectory=scripts/)"]
        MJS
        SEEDER1
        SEEDER2
        JSON
    end

    subgraph Vercel["Vercel Edge Function bundle"]
        TS
        HANDLER
    end
Loading

Reviews (1): Last reviewed commit: "hotfix(resilience): relocate sovereign-s..." | Re-trigger Greptile

Comment on lines +31 to +33
// same plain-import shape from scripts/shared/:
// `import iso2ToIso3Json from '../../../../shared/iso2-to-iso3.json';`
// (see _dimension-scorers.ts:1-2).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Misleading comment — example path doesn't match claimed pattern

The comment says "Sibling modules use the same plain-import shape from scripts/shared/" and then cites _dimension-scorers.ts:1-2 as evidence. But _dimension-scorers.ts actually imports from ../../../../shared/iso2-to-iso3.json (the repo-level shared/ directory), not scripts/shared/. The import in this file — ../../../../scripts/shared/sovereign-status.json — is the first server-side module to import from scripts/shared/ directly, so the cited precedent doesn't quite hold.

The import itself is correct; this is purely a comment accuracy issue.

@koala73
koala73 merged commit 2089ed5 into main Apr 26, 2026
11 checks passed
@koala73
koala73 deleted the hotfix/resilience-sovereign-status-bundle-path branch April 26, 2026 22:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant