06 — The implementation status matrix
The headline doc. If you read only one file in this onboarding suite, read this one. It is the single source of truth for what is built vs planned.
Tags follow docs/STATUS_LEGEND.md:
Live— implemented, runtime-verified on at least one host.Partial— at one layer but not end-to-end. Look at notes.Planned— design only; no code.Deferred— was planned, intentionally postponed.Stub— directory exists, process boots, does nothing useful.Deprecated— exists but slated for removal; named replacement.
Product capabilities
| Capability | Status | What blocks Live | Where to look |
|---|---|---|---|
| Payroll | Planned | No domain package, no engine, no API | (no code) |
| Wallet | By design (derived) | Per ADR-006 not a separate entity | balance derived from ledger_account_balance view |
| EWA — eligibility | Live | — | packages/ewa/backend/application/get-eligibility.usecase.ts |
| EWA — request | Partial | Go ledger gRPC server must be running | packages/ewa/backend/application/request-ewa.usecase.ts |
| EWA — disburse | Partial | Same (ledger) + integration-gateway for real mobile-money payout | packages/ewa/backend/application/disburse-ewa.usecase.ts |
| Lending — quote | Live | — | packages/lending/backend/application/quote-loan.usecase.ts |
| Lending — request | Partial | Go ledger gRPC server | packages/lending/backend/application/request-loan.usecase.ts |
| Lending — disburse | Partial | Same | packages/lending/backend/application/disburse-loan.usecase.ts |
| Lending — repayment schedule | Live | — | packages/lending/backend/domain/repayment-schedule.ts (flat-rate, exact-santim Money.allocate) |
| BNPL | Planned | No domain package | (no code) |
| Savings | Planned | No domain package | (no code) |
| Equb | Planned | No domain package; legacy Prisma model only | apps/api/prisma/schema.prisma (Equb model, no code consuming it) |
| KYC | Planned | No code; partial fields on Employee | Employee.kycStatus field exists |
| Risk / underwriting | Partial | Lending has an underwriting policy in domain; no external risk signals | packages/lending/backend/domain/underwriting.ts |
Infrastructure capabilities
| Capability | Status | Notes |
|---|---|---|
| Prisma migrations apply clean (9 migrations) | Live | Runtime-verified |
| Postgres schema for monolith | Live | |
| Postgres RLS on 5 financial tables | Live | Forced + verify-guarded; ADR-013 |
| Idempotency at the ledger DB level | Live | UNIQUE constraint + fingerprint reconciliation; runtime-proven |
| Idempotency at the API gateway | Partial | idempotency_record table + PrismaIdempotencyStore Live; not all routes enforce the header yet |
Audit trail (audit_entry, in-txn) | Live | EWA + lending paths emit |
| Outbox table + worker | Live | Worker disabled by default; gated on KAFKA_BROKERS + OUTBOX_PUBLISHER_ENABLED |
| BYPASSRLS publisher role | Live | Provisioning SQL at infra/sql/00_create_outbox_publisher_role.sql |
| Append-only triggers (UPDATE/DELETE blocked on ledger tables) | Live | Runtime-proven |
| Deferred balance trigger | Live | Runtime-proven (raises at COMMIT on unbalanced) |
Partial unique index on reverses_transaction_id (double-reversal lockout) | Live | Runtime-proven |
Health probes (/healthz, /readyz) | Live | Real Postgres/Redis/Kafka/ledger probes |
| Startup checks (fail-fast on critical deps) | Live | |
Prometheus /metrics | Live | Custom + Node defaults, fintech-tuned histogram buckets |
| OpenTelemetry tracing | Live (gated) | Active when OTEL_EXPORTER_OTLP_ENDPOINT set; no backend wired locally |
| Pino structured logging | Live | trace_id/span_id correlation |
| Two-language ceiling (TS + Go) | Live | ADR-010; enforced socially + by review |
| Nx module-boundary lint | Live | ADR-011 enforced by ESLint |
ESLint no-delete-on-financial rule | Live | Custom rule in tooling/eslint/ |
Auth capabilities
| Capability | Status | Notes |
|---|---|---|
| better-auth core (email + password) | Live | Sign-up + sign-in E2E proven |
| Better-auth Session table + cookie | Live | |
| Account table (for credentials + future OAuth) | Live | |
| Verification table (for OTPs + email-verify codes) | Live | |
Default-deny AuthGuard (APP_GUARD) | Live | @Public() opt-out on health/metrics/root |
SessionMiddleware → req.user.businessId | Live | From Session.activeOrganizationId |
| Organization plugin | Live | Tables + plugin loaded |
Business.create → atomic Organization creation | Live | Same $transaction |
| Backfill: existing Business → matching Organization | Live | Migration 20260526050000 with verify guard |
| Member row auto-create on first sign-up | Planned | Manual today (no Member yet → activeOrganizationId is NULL → tenant context empty → financial routes 500) |
| Auto-set-active-org on single-org users | Planned | Client must call /api/auth/organization/set-active |
| Phone OTP flow (better-auth phoneNumber plugin) | Partial | Plugin loaded, LoggingSmsSender dev-only |
| AfricasTalking SMS provider | Planned | Stub interface ready; ~50 lines + tests away |
| 2FA (TOTP) via better-auth twoFactor plugin | Planned | TwoFactor table exists, plugin NOT wired |
| WebAuthn / passkeys | Planned | |
| OAuth (Google, GitHub, etc.) | Planned | better-auth supports it; not configured |
Service-level status
| Service | Status | What works | What's missing |
|---|---|---|---|
apps/api (NestJS monolith) | Live | Full request lifecycle, health, metrics, auth, EWA + lending controllers, business + employee CRUD | Domain implementations beyond EWA + lending |
services/ledger | Partial | Schema + 5 DB invariants Live (runtime-proven). Go server code written for PostTransaction, GetBalance, Reverse, GetEntries, ReconcileAccount. | Go binary Compile-only on hosts without Go toolchain. buf generate not run. No production deployment. |
services/integration-gateway | Stub | /health only | Every bank/wallet adapter (CBE, Telebirr, Awash, M-Pesa, etc.) |
services/notifications | Stub | /health only | Kafka consumer, SMS adapter, email adapter, push adapter, retry/DLQ |
Frontend status
| App | Pages | Status | What's missing |
|---|---|---|---|
apps/admin-web | 36 | Partial — UI Live, API Planned | All data is hardcoded mocks; no fetch calls; no real auth state |
apps/employer-web | 29 | Partial — UI Live, API Planned | Same |
apps/employee-web | 13 | Partial — UI Live, API Planned | Same; auth context is localStorage stub |
apps/fi-web | 10 | Partial — UI Live, API Planned | Same; backend domain not built |
apps/merchant-web | 9 | Partial — UI Live, API Planned | Same; BNPL domain doesn't exist |
apps/docs-web | (default) | Stub | Docusaurus template only |
Packages (packages/shared/)
| Package | Status | LOC | Importers | Notes |
|---|---|---|---|---|
shared/money | Live | 305 | 39 | Core financial primitive |
shared/idempotency | Live | 111 | 15 | |
shared/audit | Live | 71 | 15 | |
shared/events | Live | 117 | 18 | |
shared/tenant-context | Live | 75 | 17 | AsyncLocalStorage-backed |
shared/database | Live | 34 | 15 | Minimal but functional |
shared/logging | Live | 71 | 6 | |
shared/config | Live | 118 | 10 | Zod env validation |
shared/validation | Partial | 464 | 4 | Has more schemas than current consumers |
shared/ui | Live (code) | 2,624 | 73 | ZERO TESTS — biggest blast-radius untested code in the repo |
shared/auth | Deprecated | 61 | 4 | Superseded by better-auth; remove after audit confirms no prod use |
Bounded contexts (packages/<domain>/)
| Domain | Backend status | LOC | Tests |
|---|---|---|---|
ewa | Partial | 1,463 | 11 unit tests pass |
lending | Partial | 1,727 | 7 unit tests pass |
bnpl | (no folder) | — | — |
savings | (no folder) | — | — |
equb | (no folder) | — | — |
payroll | (no folder) | — | — |
kyc | (no folder) | — | — |
risk | (no folder) | — | — |
identity | (no folder; better-auth is the impl) | — | — |
tenancy | (no folder; ADR-013 + middleware is the impl) | — | — |
wallet | (no folder; derived from ledger per ADR-006) | — | — |
Documentation status
| Doc | Status | Notes |
|---|---|---|
README.md | Live | Status-tagged |
CLAUDE.md | Live | Status-tagged |
PROJECT_STRUCTURE.md | Live | Status-tagged |
SECURITY_CONTROLS.md | Live | Status-tagged; per-section status snapshot at top |
INTERN_PROGRAM.md | Live | Path renames normalised; baseline section added |
docs/STATUS_LEGEND.md | Live | The canonical legend |
docs/adr/ ADR-001..013 | Live | All accepted |
docs/adr/ADR-013-tenant-isolation.md | Live | New canonical RLS doc |
docs/architecture/restructure-2026-05.md | Live (historical) | Tagged |
docs/onboarding/*.md | Live | This suite |
docs/runbooks/ | Stub | 6 priority runbooks listed, none filled |
docs/security/compliance-ethiopia.md | Planned | Will carve out from archive in Phase 2C |
docs/archive/CORE_BANKING_MICROSERVICES_PLAN.md | Deprecated (carve-out pending) | Contains regulatory research to preserve |
docs/archive/{DEMOZPAY_ARCHITECTURE,SYSTEM_DOCUMENTATION,NX_WORKSPACE_EXPLAINED,theme-system-plan}.md | Deprecated (delete pending) | Phase 2C |
The single largest blocker
If you fix one thing this week, fix this:
The Go ledger is Compile-only on most hosts because there's no
Go toolchain installed and buf generate hasn't been run. The
verification harness at services/ledger/test/verify.sh is set up
to do all of this — one command on a Go-equipped box closes the
gap.
Closing that gap promotes:
- 5 ledger RPCs from
Partial→Live - EWA disburse from
Partial→Live - Lending disburse from
Partial→Live - The whole
payroll → wallet → EWAuser journey from "fails at ledger call" to "actually moves money"
Second-largest blockers (in order)
- No Member auto-creation on first sign-up. Today a new
UserhasactiveOrganizationId = NULL, so tenant context is empty and financial routes 500. Fix: on Business creation (or on first Employee invite), create aMemberrow linking the new User to the Organization; auto-set active. - AfricasTalking SMS provider. ~50 lines + tests. Without it,
phone-OTP login can't be used in production. The
LoggingSmsSenderwarns + drops inNODE_ENV=production. - Web app → API integration. All 5 frontends use hardcoded mocks. This is at least one focused sprint per app.
- CI integration tests against a real Postgres. Today CI runs
lint + build + unit tests. The hermetic harness at
services/ledger/test/verify.shis the right pattern; expand it toapps/apimigrations + a few EWA / lending probes. - Runbooks. 6 listed, 0 filled. On-call landmine.
/api/metricsis unauthenticated. IP-allowlist or token-gate before prod.
Continue reading
Next: 07-rules-and-patterns.md —
the rules you must follow to safely add code to this platform.