Summary
Found while running the full TS stack end-to-end (Postgres + @st/api + bot DRY_RUN) off the merged ts-rewrite-plan (PRs #3/#5/#6/#7).
The bot's bulk markets write-through fails validation on every call:
PUT /markets -> 400 FST_ERR_VALIDATION "body must be array"
persistence: telemetry write dropped after retries (label=markets, status=400)
Root cause (contract mismatch)
- Bot (
packages/bot/src/clients/persistence.ts): putMarkets(markets: Record<string, Market>) → fireAndForget('PUT', '/markets', markets, 'markets') sends a Record/object { wp: market, ... }.
- API (
packages/api/src/routes/markets.ts): PUT /markets bulk body schema is BulkPutBody = Type.Array([{ waypoint, data }]) — expects an array of {waypoint,data}.
So the shapes have never matched. Because markets telemetry is fire-and-forget with retry, the failure was swallowed (logged at warn, bot continues), which is why it went unnoticed.
Severity / scope
- Pre-existing, not a regression from the galaxy/scan-budget/coverage PRs — the persistence client (wave-2
718a132) and the markets route schema have disagreed since before that work.
- Low severity: telemetry only; the bot keeps trading. But it means bulk market snapshots are not being persisted to the API (per-waypoint
PUT /markets/:wp uses a different, matching {data} schema and presumably still works).
Fix options
- Make
putMarkets serialize the Record into the array shape [{ waypoint, data }] the bulk endpoint expects (preferred — keep the bulk endpoint).
- Or change
PUT /markets to accept the Record shape.
Add a contract test (bot persistence ↔ api schema) so the two can't drift again.
Repro
docker compose up (or local Postgres + @st/api + node packages/bot/dist/main.js with DRY_RUN=1); watch the API log for PUT /markets 400 'body must be array'.
Summary
Found while running the full TS stack end-to-end (Postgres +
@st/api+ bot DRY_RUN) off the mergedts-rewrite-plan(PRs #3/#5/#6/#7).The bot's bulk markets write-through fails validation on every call:
Root cause (contract mismatch)
packages/bot/src/clients/persistence.ts):putMarkets(markets: Record<string, Market>)→fireAndForget('PUT', '/markets', markets, 'markets')sends a Record/object{ wp: market, ... }.packages/api/src/routes/markets.ts):PUT /marketsbulk body schema isBulkPutBody = Type.Array([{ waypoint, data }])— expects an array of{waypoint,data}.So the shapes have never matched. Because markets telemetry is fire-and-forget with retry, the failure was swallowed (logged at warn, bot continues), which is why it went unnoticed.
Severity / scope
718a132) and the markets route schema have disagreed since before that work.PUT /markets/:wpuses a different, matching{data}schema and presumably still works).Fix options
putMarketsserialize the Record into the array shape[{ waypoint, data }]the bulk endpoint expects (preferred — keep the bulk endpoint).PUT /marketsto accept the Record shape.Add a contract test (bot persistence ↔ api schema) so the two can't drift again.
Repro
docker compose up(or local Postgres +@st/api+node packages/bot/dist/main.jswithDRY_RUN=1); watch the API log forPUT /markets 400 'body must be array'.