Skip to main content

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

CapabilityStatusWhat blocks LiveWhere to look
PayrollPlannedNo domain package, no engine, no API(no code)
WalletBy design (derived)Per ADR-006 not a separate entitybalance derived from ledger_account_balance view
EWA — eligibilityLivepackages/ewa/backend/application/get-eligibility.usecase.ts
EWA — requestPartialGo ledger gRPC server must be runningpackages/ewa/backend/application/request-ewa.usecase.ts
EWA — disbursePartialSame (ledger) + integration-gateway for real mobile-money payoutpackages/ewa/backend/application/disburse-ewa.usecase.ts
Lending — quoteLivepackages/lending/backend/application/quote-loan.usecase.ts
Lending — requestPartialGo ledger gRPC serverpackages/lending/backend/application/request-loan.usecase.ts
Lending — disbursePartialSamepackages/lending/backend/application/disburse-loan.usecase.ts
Lending — repayment scheduleLivepackages/lending/backend/domain/repayment-schedule.ts (flat-rate, exact-santim Money.allocate)
BNPLPlannedNo domain package(no code)
SavingsPlannedNo domain package(no code)
EqubPlannedNo domain package; legacy Prisma model onlyapps/api/prisma/schema.prisma (Equb model, no code consuming it)
KYCPlannedNo code; partial fields on EmployeeEmployee.kycStatus field exists
Risk / underwritingPartialLending has an underwriting policy in domain; no external risk signalspackages/lending/backend/domain/underwriting.ts

Infrastructure capabilities

CapabilityStatusNotes
Prisma migrations apply clean (9 migrations)LiveRuntime-verified
Postgres schema for monolithLive
Postgres RLS on 5 financial tablesLiveForced + verify-guarded; ADR-013
Idempotency at the ledger DB levelLiveUNIQUE constraint + fingerprint reconciliation; runtime-proven
Idempotency at the API gatewayPartialidempotency_record table + PrismaIdempotencyStore Live; not all routes enforce the header yet
Audit trail (audit_entry, in-txn)LiveEWA + lending paths emit
Outbox table + workerLiveWorker disabled by default; gated on KAFKA_BROKERS + OUTBOX_PUBLISHER_ENABLED
BYPASSRLS publisher roleLiveProvisioning SQL at infra/sql/00_create_outbox_publisher_role.sql
Append-only triggers (UPDATE/DELETE blocked on ledger tables)LiveRuntime-proven
Deferred balance triggerLiveRuntime-proven (raises at COMMIT on unbalanced)
Partial unique index on reverses_transaction_id (double-reversal lockout)LiveRuntime-proven
Health probes (/healthz, /readyz)LiveReal Postgres/Redis/Kafka/ledger probes
Startup checks (fail-fast on critical deps)Live
Prometheus /metricsLiveCustom + Node defaults, fintech-tuned histogram buckets
OpenTelemetry tracingLive (gated)Active when OTEL_EXPORTER_OTLP_ENDPOINT set; no backend wired locally
Pino structured loggingLivetrace_id/span_id correlation
Two-language ceiling (TS + Go)LiveADR-010; enforced socially + by review
Nx module-boundary lintLiveADR-011 enforced by ESLint
ESLint no-delete-on-financial ruleLiveCustom rule in tooling/eslint/

Auth capabilities

CapabilityStatusNotes
better-auth core (email + password)LiveSign-up + sign-in E2E proven
Better-auth Session table + cookieLive
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
SessionMiddlewarereq.user.businessIdLiveFrom Session.activeOrganizationId
Organization pluginLiveTables + plugin loaded
Business.create → atomic Organization creationLiveSame $transaction
Backfill: existing Business → matching OrganizationLiveMigration 20260526050000 with verify guard
Member row auto-create on first sign-upPlannedManual today (no Member yet → activeOrganizationId is NULL → tenant context empty → financial routes 500)
Auto-set-active-org on single-org usersPlannedClient must call /api/auth/organization/set-active
Phone OTP flow (better-auth phoneNumber plugin)PartialPlugin loaded, LoggingSmsSender dev-only
AfricasTalking SMS providerPlannedStub interface ready; ~50 lines + tests away
2FA (TOTP) via better-auth twoFactor pluginPlannedTwoFactor table exists, plugin NOT wired
WebAuthn / passkeysPlanned
OAuth (Google, GitHub, etc.)Plannedbetter-auth supports it; not configured

Service-level status

ServiceStatusWhat worksWhat's missing
apps/api (NestJS monolith)LiveFull request lifecycle, health, metrics, auth, EWA + lending controllers, business + employee CRUDDomain implementations beyond EWA + lending
services/ledgerPartialSchema + 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-gatewayStub/health onlyEvery bank/wallet adapter (CBE, Telebirr, Awash, M-Pesa, etc.)
services/notificationsStub/health onlyKafka consumer, SMS adapter, email adapter, push adapter, retry/DLQ

Frontend status

AppPagesStatusWhat's missing
apps/admin-web36Partial — UI Live, API PlannedAll data is hardcoded mocks; no fetch calls; no real auth state
apps/employer-web29Partial — UI Live, API PlannedSame
apps/employee-web13Partial — UI Live, API PlannedSame; auth context is localStorage stub
apps/fi-web10Partial — UI Live, API PlannedSame; backend domain not built
apps/merchant-web9Partial — UI Live, API PlannedSame; BNPL domain doesn't exist
apps/docs-web(default)StubDocusaurus template only

Packages (packages/shared/)

PackageStatusLOCImportersNotes
shared/moneyLive30539Core financial primitive
shared/idempotencyLive11115
shared/auditLive7115
shared/eventsLive11718
shared/tenant-contextLive7517AsyncLocalStorage-backed
shared/databaseLive3415Minimal but functional
shared/loggingLive716
shared/configLive11810Zod env validation
shared/validationPartial4644Has more schemas than current consumers
shared/uiLive (code)2,62473ZERO TESTS — biggest blast-radius untested code in the repo
shared/authDeprecated614Superseded by better-auth; remove after audit confirms no prod use

Bounded contexts (packages/<domain>/)

DomainBackend statusLOCTests
ewaPartial1,46311 unit tests pass
lendingPartial1,7277 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

DocStatusNotes
README.mdLiveStatus-tagged
CLAUDE.mdLiveStatus-tagged
PROJECT_STRUCTURE.mdLiveStatus-tagged
SECURITY_CONTROLS.mdLiveStatus-tagged; per-section status snapshot at top
INTERN_PROGRAM.mdLivePath renames normalised; baseline section added
docs/STATUS_LEGEND.mdLiveThe canonical legend
docs/adr/ ADR-001..013LiveAll accepted
docs/adr/ADR-013-tenant-isolation.mdLiveNew canonical RLS doc
docs/architecture/restructure-2026-05.mdLive (historical)Tagged
docs/onboarding/*.mdLiveThis suite
docs/runbooks/Stub6 priority runbooks listed, none filled
docs/security/compliance-ethiopia.mdPlannedWill carve out from archive in Phase 2C
docs/archive/CORE_BANKING_MICROSERVICES_PLAN.mdDeprecated (carve-out pending)Contains regulatory research to preserve
docs/archive/{DEMOZPAY_ARCHITECTURE,SYSTEM_DOCUMENTATION,NX_WORKSPACE_EXPLAINED,theme-system-plan}.mdDeprecated (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 PartialLive
  • EWA disburse from PartialLive
  • Lending disburse from PartialLive
  • The whole payroll → wallet → EWA user journey from "fails at ledger call" to "actually moves money"

Second-largest blockers (in order)

  1. No Member auto-creation on first sign-up. Today a new User has activeOrganizationId = NULL, so tenant context is empty and financial routes 500. Fix: on Business creation (or on first Employee invite), create a Member row linking the new User to the Organization; auto-set active.
  2. AfricasTalking SMS provider. ~50 lines + tests. Without it, phone-OTP login can't be used in production. The LoggingSmsSender warns + drops in NODE_ENV=production.
  3. Web app → API integration. All 5 frontends use hardcoded mocks. This is at least one focused sprint per app.
  4. CI integration tests against a real Postgres. Today CI runs lint + build + unit tests. The hermetic harness at services/ledger/test/verify.sh is the right pattern; expand it to apps/api migrations + a few EWA / lending probes.
  5. Runbooks. 6 listed, 0 filled. On-call landmine.
  6. /api/metrics is 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.