WM Help Desk
A multi-queue help desk built for a registered investment adviser, with books-and-records controls designed in from the start: role-based access, PII redaction before anything is stored, SLA tracking and a hash-chained audit ledger.
Overview
A multi-queue help desk built for a registered investment adviser, with books-and-records controls designed in from the start: role-based access, PII redaction before anything is stored, SLA tracking and a hash-chained audit ledger.
The problem
Client service ran on a general-purpose ticketing tool. Internal notes and client replies sat side by side, account numbers and Social Security numbers landed in free text, and there was no way to show a reviewer that a record had not been changed after the fact.
What I built
- Queues for client service, money movement, operations and compliance, with access set by group membership.
- Internal notes in a separate visibility channel that client-facing views cannot query.
- PII redaction before the write, so the database never holds a full SSN or account number.
- SLA clocks with On track, Warning and Breached states, and alerts before a breach.
- An append-only audit ledger in which every entry carries the hash of the one before it.
- A phone-first interface with a single layout breakpoint, since most triage happens away from a desk.
Key decisions
Redact on the way in
Masking at display time still leaves raw values in backups and exports. Redacting before the write means there is nothing to leak.
Enforce visibility where data is read
Internal notes are filtered in the query layer, so a new client-facing screen cannot expose one by accident.
Hash-chain the audit log
Each row stores the previous row’s hash. Re-verifying the chain proves no entry was edited or removed.
Write the contract first
The architecture document defines 15 invariants, each backed by a test. A change that breaks one fails CI.
Architecture
Next.js · TypeScript · Prisma · Postgres · Auth.js · Vitest
The demo
The demo runs the same application with sign-in replaced by a user picker. Email intake, chat alerts and outbound webhooks are captured inside the demo instead of being sent.
Live demo in progress
This demo is being rebuilt on synthetic data. This page will link to it when it ships.