[BTAPI-70] Fix Wealthsimple externalId instability across pending→posted #80

Merged
chris merged 6 commits from bug/BTAPI-70 into main 2026-08-29 17:42:05 -06:00
Owner

Ticket

BTAPI-70 Wealthsimple canonicalId is not stable across pending→posted; every settling charge duplicates

Summary

  • Wealthsimple injects an extra posting-token segment into a purchase's canonicalId on settlement, so the sync engine's dedupe key (previously the raw canonicalId) missed on the posted variant and inserted a duplicate transaction instead of revising the pending row in place — the same miss also fired spurious wealthsimplePendingVanished alerts.
  • Added deriveExternalId (server/lib/wealthsimple/external-id) to derive a stable dedupe key from the structurally-fixed portion of canonicalId, falling back to the raw id (logged) on any unrecognized shape. The raw canonicalId is now carried alongside on Transaction for traceability.
  • Added a one-time ws:migrate-external-ids CLI to re-derive externalId on existing rows written before this fix — must be run once, with the sync container stopped, before the next scheduled sync (see the updated root CLAUDE.md and server/lib/wealthsimple/CLAUDE.md for the exact runbook).
  • Passed a full pre-PR architecture/correctness/conventions/test-coverage/security review; all confirmed findings were fixed in a follow-up commit.

Known limitations (flagged, not fixed — out of scope for this ticket)

  • An unrecognized Wealthsimple id shape is logged but not alerted to the app. The ticket's acceptance criteria only require logging; alerting would need a new AlertKind and wiring, which is a genuine scope increase. Worth a follow-up ticket if it's ever seen in practice.
  • The one-time migration doesn't rewrite WealthsimpleSyncState.reportedMissingExternalIds (still raw ids after migration) — self-correcting on the next tick (at most one duplicate vanished-pending alert), not worth the added migration scope.
## Ticket [BTAPI-70](http://192.168.2.100:7123/home/browse/BTAPI-70/) Wealthsimple canonicalId is not stable across pending→posted; every settling charge duplicates ## Summary - Wealthsimple injects an extra posting-token segment into a purchase's `canonicalId` on settlement, so the sync engine's dedupe key (previously the raw `canonicalId`) missed on the posted variant and inserted a duplicate transaction instead of revising the pending row in place — the same miss also fired spurious `wealthsimplePendingVanished` alerts. - Added `deriveExternalId` (`server/lib/wealthsimple/external-id`) to derive a stable dedupe key from the structurally-fixed portion of `canonicalId`, falling back to the raw id (logged) on any unrecognized shape. The raw `canonicalId` is now carried alongside on `Transaction` for traceability. - Added a one-time `ws:migrate-external-ids` CLI to re-derive `externalId` on existing rows written before this fix — **must be run once, with the `sync` container stopped, before the next scheduled sync** (see the updated root `CLAUDE.md` and `server/lib/wealthsimple/CLAUDE.md` for the exact runbook). - Passed a full pre-PR architecture/correctness/conventions/test-coverage/security review; all confirmed findings were fixed in a follow-up commit. ## Known limitations (flagged, not fixed — out of scope for this ticket) - An unrecognized Wealthsimple id shape is logged but not alerted to the app. The ticket's acceptance criteria only require logging; alerting would need a new `AlertKind` and wiring, which is a genuine scope increase. Worth a follow-up ticket if it's ever seen in practice. - The one-time migration doesn't rewrite `WealthsimpleSyncState.reportedMissingExternalIds` (still raw ids after migration) — self-correcting on the next tick (at most one duplicate vanished-pending alert), not worth the added migration scope.
Also seeds canonicalId in the sync-cli and trigger test builders, whose WealthsimpleActivity
fixtures otherwise left it undefined -- which round-tripped through Mongo as a value that
differed from itself on the next tick and broke idempotency.
[BTAPI-70] Address pre-PR review.
All checks were successful
server / check (pull_request) Successful in 32s
plane-sync / sync (pull_request) Successful in 2s
c37ba0a292
chris merged commit d3d57b23b9 into main 2026-08-29 17:42:05 -06:00
chris deleted branch bug/BTAPI-70 2026-08-29 17:42:05 -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!80
No description provided.