route.mjs / config.mjs: top-level data-file parse runs outside the dispatch guard (raw crash instead of {ok:false}) #40

Closed
opened 2026-07-23 14:29:39 +00:00 by crenshawdev · 0 comments
crenshawdev commented 2026-07-23 14:29:39 +00:00 (Migrated from github.com)

Severity: medium · Area: seam-contract · surfaced in the post-v1.2.0 cross-model review sweep. route.mjs was flagged by all three reviewers; config.mjs by one.

What happens

route.mjs and config.mjs both promise, in their header, to never crash the spine: on any problem they emit one JSON line {ok:false,...} so the caller degrades gracefully. But each parses its shipped data file at module top level, before the try/catch that guards command dispatch. A missing or malformed data file (partial install, bad edit, botched merge) therefore throws a raw SyntaxError with no JSON on stdout — the caller parsing the single-JSON-line contract gets nothing.

Mechanism (verified against source)

  • cadence-core/bin/route.mjs:32: const TABLE = JSON.parse(readFileSync(join(HERE, '..', 'route-table.json'), 'utf8')); at module load; the dispatch guard is further down. Contract stated at route.mjs:9-10 ("Never blocks the spine: on any problem it returns {ok:false,...}").
  • cadence-core/bin/config.mjs:30-32: const SCHEMA = JSON.parse(readFileSync(join(HERE, '..', 'config.schema.json'), 'utf8')).keys; at module load; the dispatch try starts at config.mjs:213.

Trigger for both: the data file is absent or not valid JSON.

Fix direction

Move each top-level JSON.parse(readFileSync(...)) into (or behind) the command guard, so a bad data file degrades to the contracted {ok:false, reason, detail} line instead of an uncaught stack. A shared helper (load-or-fail returning the structured error) would cover both.

Out of scope (intentionally)

self-verify.mjs:149 loads config.schema.json the same way, but self-verify is a dev/CI tool where a loud crash on a corrupt shipped schema is acceptable; it makes no "never blocks the spine" promise. Left as-is by design.

**Severity:** medium · **Area:** seam-contract · surfaced in the post-v1.2.0 cross-model review sweep. `route.mjs` was flagged by all three reviewers; `config.mjs` by one. ## What happens `route.mjs` and `config.mjs` both promise, in their header, to never crash the spine: on any problem they emit one JSON line `{ok:false,...}` so the caller degrades gracefully. But each parses its shipped data file at **module top level**, before the try/catch that guards command dispatch. A missing or malformed data file (partial install, bad edit, botched merge) therefore throws a raw `SyntaxError` with no JSON on stdout — the caller parsing the single-JSON-line contract gets nothing. ## Mechanism (verified against source) - `cadence-core/bin/route.mjs:32`: `const TABLE = JSON.parse(readFileSync(join(HERE, '..', 'route-table.json'), 'utf8'));` at module load; the dispatch guard is further down. Contract stated at `route.mjs:9-10` ("Never blocks the spine: on any problem it returns {ok:false,...}"). - `cadence-core/bin/config.mjs:30-32`: `const SCHEMA = JSON.parse(readFileSync(join(HERE, '..', 'config.schema.json'), 'utf8')).keys;` at module load; the dispatch `try` starts at `config.mjs:213`. Trigger for both: the data file is absent or not valid JSON. ## Fix direction Move each top-level `JSON.parse(readFileSync(...))` into (or behind) the command guard, so a bad data file degrades to the contracted `{ok:false, reason, detail}` line instead of an uncaught stack. A shared helper (load-or-fail returning the structured error) would cover both. ## Out of scope (intentionally) `self-verify.mjs:149` loads `config.schema.json` the same way, but self-verify is a dev/CI tool where a loud crash on a corrupt shipped schema is acceptable; it makes no "never blocks the spine" promise. Left as-is by design.
Sign in to join this conversation.
No description provided.