fix(server): error-handling and config-validation batch #6

Merged
pregno merged 4 commits from fix/server-smallfixes into main 2026-06-13 01:29:01 +02:00
Owner

Four small fixes from the repo-wide review, one commit each:

  1. Position-open 409 narrowed. The unique-index backstop caught ANY error and reported "position already open" — disk-full or closed-DB errors became 409s. Now only SqliteError.code === "SQLITE_CONSTRAINT_UNIQUE" (verified empirically against the real partial index) maps to 409; everything else propagates as 500. The race-past-the-pre-check path is covered by a test that stubs the pre-check to miss and hits the real index.

  2. GET /api/positions fetches prices in parallel, deduped per (instrumentClass, symbol) (same pattern as OutcomeTracker.fill). Per-position error semantics unchanged: a failed price still yields currentPrice: null for that position only.

  3. Config validation. CATALYST_PROVIDER is validated against the allowed union (typos warn loudly and fall back to none instead of silently degrading); port/pollIntervalMs/discoveryIntervalMs/requestTimeoutMs use numPos so PORT="" no longer yields port 0 and POLL_INTERVAL_MS=0 no longer spins; confirmationWindow is validated against the Yahoo CHART_INTERVALS via resolveChartInterval() (warn + fall back to 15m) — Binance keeps the raw value since it has its own interval grammar.

  4. PUT /api/settings 400s return a generic { error: "invalid settings" } instead of leaking zod issue internals; issues are logged server-side.

Tests: 56 files / 320 passing, typecheck clean.

Four small fixes from the repo-wide review, one commit each: 1. **Position-open 409 narrowed.** The unique-index backstop caught ANY error and reported "position already open" — disk-full or closed-DB errors became 409s. Now only `SqliteError.code === "SQLITE_CONSTRAINT_UNIQUE"` (verified empirically against the real partial index) maps to 409; everything else propagates as 500. The race-past-the-pre-check path is covered by a test that stubs the pre-check to miss and hits the real index. 2. **`GET /api/positions` fetches prices in parallel**, deduped per `(instrumentClass, symbol)` (same pattern as `OutcomeTracker.fill`). Per-position error semantics unchanged: a failed price still yields `currentPrice: null` for that position only. 3. **Config validation.** `CATALYST_PROVIDER` is validated against the allowed union (typos warn loudly and fall back to `none` instead of silently degrading); `port`/`pollIntervalMs`/`discoveryIntervalMs`/`requestTimeoutMs` use `numPos` so `PORT=""` no longer yields port 0 and `POLL_INTERVAL_MS=0` no longer spins; `confirmationWindow` is validated against the Yahoo `CHART_INTERVALS` via `resolveChartInterval()` (warn + fall back to `15m`) — Binance keeps the raw value since it has its own interval grammar. 4. **`PUT /api/settings` 400s return a generic `{ error: "invalid settings" }`** instead of leaking zod issue internals; issues are logged server-side. Tests: 56 files / 320 passing, typecheck clean.
The unique-index backstop caught every error from openPosition and
returned 409 "position already open", masking real failures. Check for
better-sqlite3's SqliteError code SQLITE_CONSTRAINT_UNIQUE (verified
empirically against the partial unique index) and rethrow anything else
so it surfaces as 500.
One awaited fetch per position made response time grow linearly with
open positions. Fan out with Promise.all and dedupe per
(instrumentClass, symbol) pair, mirroring OutcomeTracker.fill. Failed
prices still yield null currentPrice/plPct per position (PriceSource
never throws), so per-position error semantics are unchanged.
- CATALYST_PROVIDER was cast to its union unchecked; validate against
  the allowed values and warn + fall back to "none".
- num() accepts "" -> 0 and non-positives, so PORT="" gave port 0 and
  POLL_INTERVAL_MS=0 a spinning poll loop; use the existing numPos()
  for port, pollIntervalMs, discoveryIntervalMs, requestTimeoutMs.
- CONFIRMATION_WINDOW was cast to ChartInterval unchecked, silently
  degrading every equity confirmation on a Yahoo-invalid value;
  validate against CHART_INTERVALS and warn + fall back to 15m.
Every other route returns a generic { error } shape; the settings route
echoed parsed.error.issues to the client. Return the generic error and
log the issues server-side instead.
pregno force-pushed fix/server-smallfixes from d459bd2049 to d5cff16d6a 2026-06-13 01:27:55 +02:00 Compare
pregno merged commit 4fc7c5a279 into main 2026-06-13 01:29:01 +02:00
pregno deleted branch fix/server-smallfixes 2026-06-13 01:29:01 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pregno/polymarket-screener!6
No description provided.