Skip to main content

Repo restructure — May 2026

This document records the structural refactor that ran across May 2026 and the order it landed in. Status: the refactor is complete; this file is now historical reference. The live architecture is described in:

  • README.md (entry point)
  • CLAUDE.md (terse map)
  • docs/STATUS_LEGEND.md (what Live / Partial / Planned / Stub mean)
  • docs/adr/ (decisions; ADR-001…013)

Status tags below follow docs/STATUS_LEGEND.md.

Why

The previous structure (libs/ heavy, apps/ mixing FE + BE + Go services, vague Nx tags) had:

  • Cognitive overload — engineers couldn't tell where to add a new module.
  • Placeholder folders without code.
  • Vague ownership — libs/api/* placeholders, libs/shared/* README-only stubs.
  • Process composition (apps/) mixed with deploy units (Go services).
  • Frontend names that didn't say who the audience was (business, client, fi).

Final structure

apps/ composition roots only — bootstrap + wiring
api/ NestJS modular monolith
admin-web/ platform admin
employer-web/ employer admin (was: business)
employee-web/ employee app (was: client)
fi-web/ financial institution
merchant-web/ BNPL / merchant (was: bnpl-partner)
docs-web/ public docs site (was: docs)
*-e2e/ Playwright per app

services/ independently deployable
ledger/ Go — double-entry journal
integration-gateway/ Go — bank/wallet adapters
notifications/ worker — SMS/email/push

packages/
<domain>/ bounded contexts (only when code exists)
backend/{domain,application,infrastructure,presentation}
frontend/ domain-aware FE primitives (optional)
contracts/ how others talk to this domain
schemas/ Zod validation
tests/ integration tests
shared/
money/ exact-integer Money type
idempotency/ Idempotency-Key store + interceptor
audit/ audit-log emitter
events/ transactional outbox + Kafka publisher
tenant-context/ AsyncLocalStorage tenant ctx
auth/ JWT, guards, decorators
database/ Prisma client wrapper
logging/ pino + PII redaction
validation/ Zod helpers
ui/ React design system
contracts/
grpc/ .proto files
openapi/ public REST contracts

infra/ terraform, helm, argocd, docker (local dev)
tooling/ eslint custom rules, prisma-lint, generators
docs/ adr, runbooks, architecture, onboarding, security

Phases

#WhatStatus
0This doc + ADR-002Live (landed May 2026)
1apps/{ledger,integration-gateway,notifications}services/*Live — three Go service skeletons in services/. Note: ledger is Partial (DB Live, Go server Compile-only); integration-gateway and notifications are Stub (/health only).
2Frontend renames to *-webLive
3Create packages/shared/*; move libs/shared/money + libs/uiLive
4packages/contracts/{grpc,openapi} + first .proto filesPartial.proto sources Live, Go stubs require buf generate (not yet run on a Go-equipped host).
5–7Domain extraction (identity, tenancy, workforce, payroll, wallet, kyc, risk)Planned. Two domains have shipped outside this group: ewa (Partial) and lending (Partial). The rest are unbuilt.
8New Nx tag + boundary rulesLive — ESLint module boundaries enforced.
9Docs rewrite (CLAUDE.md, README.md)Live. Status tags added by Phase 2A of the May 2026 doc consolidation.
10Delete empty libs/; clean tsconfig path aliasesLive

Capabilities added AFTER this restructure (covered by ADRs 006…013, not this doc):

CapabilityStatus
Ledger schema + DB-enforced double-entry / append-only / RLSLive
Ledger Go server (PostTransaction, GetBalance, Reverse, GetEntries, ReconcileAccount)Partial — code written + DB invariants proven; Go binary not yet built on this host
Tenant RLS migration with verify guardLive (ADR-013)
BYPASSRLS publisher role + OUTBOX_DATABASE_URLLive
better-auth (email + phone + org plugins)Partial — email sign-up Live; phone OTP Partial (stub SMS sender); 2FA Planned
/healthz + /readyz + Prometheus /metricsLive
Outbox publisher workerLive (disabled by default; gated by OUTBOX_PUBLISHER_ENABLED)

Non-goals for this restructure

  • Migrating Decimal(15,2) money columns to NUMERIC(20,0) santim. Separate migration.
  • Removing the Wallet.balance and LedgerAccount.balance derived columns. Separate migration.
  • Renaming BNPLPartner Prisma model to Merchant. Separate migration.
  • Renaming mfi enum value to fi. Separate migration.
  • Domain extraction (Phases 5–7).

These are deliberately deferred — each is a real DB migration and warrants its own ADR + PR.

How to read this repo after the restructure

  1. Want to add an HTTP endpoint? Find the right domain in packages/. If it doesn't exist yet, create it via pnpm gen:domain <name>. Endpoint goes in packages/<name>/backend/presentation/.
  2. Want to add a money primitive? Use @demoz-pay/shared-money. Don't reinvent.
  3. Want to add a new external integration? Adapter goes in services/integration-gateway/. Never in apps/api.
  4. Want to add a UI screen? Goes in apps/<audience>-web/. Reusable design-system components go in packages/shared/ui/.
  5. Want to change the ledger? Go service in services/ledger/. Contract is packages/contracts/grpc/ledger.proto.

ADRs landed alongside this restructure

  • ADR-002 — packages/ over libs/; target folder layout
  • ADR-003 — Domain package shape (domain/application/infrastructure/presentation)
  • ADR-004 — Frontend rename: *-web suffix
  • ADR-005 — Money is NUMERIC(20,0) santim; floats forbidden
  • ADR-006 — Ledger is sole source of money truth
  • ADR-007 — Idempotency-Key required on money-moving POSTs
  • ADR-008 — Audit + outbox in the same DB transaction
  • ADR-009 — No DELETE on financial rows
  • ADR-010 — Two-language ceiling: TypeScript + Go
  • ADR-011 — Cross-domain communication is event-only