[BTAPI-77] Source sync status from wealthsimple-api #95

Merged
chris merged 6 commits from feature/BTAPI-77 into main 2026-09-16 09:26:37 -06:00
Owner

Ticket

BTAPI-77 Source sync status from wealthsimple-api

Summary

  • Adds fetchStatus() to lib/wealthsimple/activity-feed-client, reading wealthsimple-api's GET /status (WSAPI-7) and validating it with zod, following the module's existing fetchActivityFeed conventions (shared TIMEOUT_MS, WealthsimpleApiClientError on every failure).
  • lib/sync-status's wealthsimpleStatus() now branches on Config.wealthsimpleApiEnabled: flag off keeps today's contract byte-identical (local session + local sync-state, always 200); flag on sources session/lastSyncedAt/lastResult/lastError from wealthsimple-api instead, widening session to a fourth value ('unreachable') and letting /sync/status answer 503 when session !== 'ok'. consecutiveFailures/cooldownUntil/cooldownReason/lastDoorbellTriggerAt stay locally sourced in both branches (no wealthsimple-api equivalent), factored into one shared helper so the branches can't drift.
  • A local tick() halt (from getSessionHaltReason(), unconditional regardless of the flag) always wins over the flag-on path — a locally-halted engine reports lastResult: 'halted' even if wealthsimple-api's own cron is succeeding independently.
  • Every fetchStatus() failure on the flag-on path is logged (including the operator-facing WS_API_KEY/INTERNAL_API_KEY-mismatch hint on a 401) before folding down to the generic, non-leaking 'unreachable' the app sees.
  • Documented in server/lib/wealthsimple/CLAUDE.md ("BTAPI-77: the status source") and a short pointer in root CLAUDE.md.
  • This ticket's flag-on path is inert by default — WS_API_ENABLED=false in every environment until deliberately turned on, so this PR changes no production behavior on its own.
  • Before the flag is ever turned on in production, BTAPP-53's banner needs updating to treat a 503 as a valid health report (not a request failure) and render copy for session: 'unreachable'. Not a merge blocker for this PR, per the approved plan — tracked as app-side follow-up work, no Plane ticket created here.

Test plan

  • bun --cwd=./server run verify (type-check, lint, format, format:check, full test suite) — all green
  • Manual, flag off: confirm GET /sync/status is byte-identical to today's production behavior
  • Manual, flag on: with wealthsimple-api healthy, confirm 200/session: 'ok'; stop it, confirm 503/session: 'unreachable' with a non-leaking lastError; confirm lastDoorbellTriggerAt still populates after a doorbell ring in both branches

🤖 Generated with Claude Code

## Ticket [BTAPI-77](http://192.168.2.100:7123/home/browse/BTAPI-77/) Source sync status from wealthsimple-api ## Summary - Adds `fetchStatus()` to `lib/wealthsimple/activity-feed-client`, reading wealthsimple-api's `GET /status` (WSAPI-7) and validating it with zod, following the module's existing `fetchActivityFeed` conventions (shared `TIMEOUT_MS`, `WealthsimpleApiClientError` on every failure). - `lib/sync-status`'s `wealthsimpleStatus()` now branches on `Config.wealthsimpleApiEnabled`: flag off keeps today's contract byte-identical (local session + local sync-state, always 200); flag on sources `session`/`lastSyncedAt`/`lastResult`/`lastError` from wealthsimple-api instead, widening `session` to a fourth value (`'unreachable'`) and letting `/sync/status` answer 503 when `session !== 'ok'`. `consecutiveFailures`/`cooldownUntil`/`cooldownReason`/`lastDoorbellTriggerAt` stay locally sourced in both branches (no wealthsimple-api equivalent), factored into one shared helper so the branches can't drift. - A local `tick()` halt (from `getSessionHaltReason()`, unconditional regardless of the flag) always wins over the flag-on path — a locally-halted engine reports `lastResult: 'halted'` even if wealthsimple-api's own cron is succeeding independently. - Every `fetchStatus()` failure on the flag-on path is logged (including the operator-facing `WS_API_KEY`/`INTERNAL_API_KEY`-mismatch hint on a 401) before folding down to the generic, non-leaking `'unreachable'` the app sees. - Documented in `server/lib/wealthsimple/CLAUDE.md` ("BTAPI-77: the status source") and a short pointer in root `CLAUDE.md`. - **This ticket's flag-on path is inert by default** — `WS_API_ENABLED=false` in every environment until deliberately turned on, so this PR changes no production behavior on its own. - **Before the flag is ever turned on in production, BTAPP-53's banner needs updating** to treat a 503 as a valid health report (not a request failure) and render copy for `session: 'unreachable'`. Not a merge blocker for this PR, per the approved plan — tracked as app-side follow-up work, no Plane ticket created here. ## Test plan - [x] `bun --cwd=./server run verify` (type-check, lint, format, format:check, full test suite) — all green - [ ] Manual, flag off: confirm `GET /sync/status` is byte-identical to today's production behavior - [ ] Manual, flag on: with wealthsimple-api healthy, confirm 200/`session: 'ok'`; stop it, confirm 503/`session: 'unreachable'` with a non-leaking `lastError`; confirm `lastDoorbellTriggerAt` still populates after a doorbell ring in both branches 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
[BTAPI-77] Cover the alive-but-reasonless session fallback and complete the CLAUDE.md note.
All checks were successful
server / check (pull_request) Successful in 32s
plane-sync / sync (pull_request) Successful in 3s
7311831703
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
chris merged commit e2fea04691 into main 2026-09-16 09:26:37 -06:00
chris deleted branch feature/BTAPI-77 2026-09-16 09:26:37 -06: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
chris/budget-tracker!95
No description provided.