Local-first sales operations platform: personal/team/company dashboards, annual quotas, revenue actuals, Stripe imports, HubSpot CRM sync, lead-response SLAs, call/email quality evaluation, CRM hygiene, behaviour scores and audit history.
Architecture: TypeScript modular monolith with ports and adapters. Runs entirely on a developer laptop today; moves to AWS later by swapping infrastructure adapters and configuration only — no business-module rewrites.
| Capability | Local | AWS (Phase 10) |
|---|---|---|
| Runtime | Docker Compose | ECS Fargate |
| Database | PostgreSQL container | RDS PostgreSQL |
| Queues | LocalStack SQS | Amazon SQS |
| Objects | MinIO | Amazon S3 |
| Cache | Valkey container | ElastiCache (Valkey) |
| Scheduling | Scheduler process | EventBridge Scheduler |
| Secrets | .env |
Secrets Manager |
Prerequisites: Node 20+ (22 recommended), pnpm 9, Docker Desktop.
pnpm install
pnpm devpnpm dev starts the infrastructure containers, applies migrations, seeds an
empty database, then runs web (3000), API (3001), worker and scheduler with hot
reload — and prints the development accounts:
| Account | Password | Role |
|---|---|---|
| admin@bastion.local | admin-dev-password | Administrator |
| manager@bastion.local | manager-dev-password | Manager |
| salesperson1@bastion.local | sales1-dev-password | Salesperson |
| salesperson2@bastion.local | sales2-dev-password | Salesperson |
These accounts exist only in dev-like profiles — never in staging/production.
| Command | Purpose |
|---|---|
pnpm dev |
Local development mode with hot reload |
pnpm local:up / local:down |
Start/stop infrastructure (down never deletes data) |
pnpm local:reset |
Delete all local data (asks for confirmation) |
pnpm local:integration |
Real provider APIs (HubSpot etc.) with local infra |
pnpm local:host |
Laptop-hosted pilot: production images + optional tunnel |
pnpm local:logs |
Tail container logs |
pnpm local:backup / local:restore --file <backup> |
Protected backups, tested restore |
pnpm test / test:integration / test:e2e |
Unit / containerised integration / Playwright |
pnpm lint / typecheck / build |
Static checks and builds |
pnpm db:migrate / db:seed / db:studio |
Prisma workflows |
pnpm openapi:generate |
Write docs/api/openapi.json |
pnpm deploy --stage staging|production |
AWS deploy (gated until Phase 10) |
apps/ web (Next.js), api (NestJS/Fastify), worker, scheduler, infra (CDK, Phase 10)
packages/ contracts, configuration, database, logger, observability,
api-client, ui, test-utils,
infrastructure-ports / -local / -aws ← the ports-and-adapters seam
prisma/ schema.prisma, migrations, seed.ts
scripts/ local-start/host/reset, backup/restore, deploy
infrastructure/ docker (app image), local (LocalStack init)
docs/ architecture, ADRs, operations, security, api
- development —
pnpm dev; zero-config defaults, mock CRM/AI providers. - local-integration — real HubSpot/Stripe against local infra; HTTPS tunnel
for webhooks via
PUBLIC_WEBHOOK_BASE_URL(polling covers the no-tunnel case). - local-hosted —
pnpm local:host; production containers, secure tunnel, backups. Only web/API are exposed — never PostgreSQL/MinIO/Valkey/LocalStack. - staging / production — same images on ECS Fargate (Phase 10; deploy is gated until local acceptance passes).
Three layers (all provider-neutral through packages/contracts CRM contracts):
- Webhooks —
POST /api/v1/webhooks/crm/hubspot: verify signature v3, store, dedupe, enqueue, respond fast; worker re-fetches the full record. - Incremental sync — every 10 min, per-object cursors with 15-min overlap.
- Reconciliation — nightly (48h window) and weekly/broad, plus on-demand from the admin Integrations screen.
A mock provider ships for fully offline development and provider contract tests.
Revenue ingestion is payment-source agnostic: upload Stripe, QuickBooks, bank-transfer or generic CSVs at Admin → Payment Imports — each provider has a parser adapter mapping to the same canonical payment row, so validation, dedupe (organisation + source account + transaction id), attribution and commit are identical for every rail.
Commission realises on invoiced amounts actually paid — never booked ACV.
The engine in packages/contracts/src/commission.ts implements the sales
commission plan: base rate × accelerator on accumulated paid-vs-quota
(1.0/1.1/1.2/1.5/1.6 tiers, payments split exactly at boundaries), partner-rate
deduction, Public Plan exclusion, excluded-product 5% cap, multiyear 5% of
subsequent-year payments (no quota credit), guaranteed-commission advances,
£250 minimum payout with carry-over, and quarterly/bi-annual/annual attainment
gates. Deal type, partner rate, plan flags and contract year are read from
optional (metadata) CSV columns. Statements live at /commission
(GET /api/v1/me/commission); plan configuration at Admin → Commission Plans.
Integer minor units everywhere. £1,000,000.00 = 100000000. Default currency GBP.
GET /health/live, /health/ready, /health/dependencies (Postgres, queue,
object storage, cache, worker/scheduler heartbeats — no secrets exposed).
docs/architecture/overview.md— module map and data flowsdocs/adr/0001-local-first-ports-and-adapters.mddocs/operations/local-development.md,docs/operations/laptop-hosting.mddocs/security/security-model.md