DemozPay — Real System State (Principal Architect Audit)
Snapshot: 2026-05-29 Author: Principal Architect / Acting CTO Replaces: the optimistic closure narrative in
SYSTEM_GAP.md§0.
SYSTEM_GAP.mdwas a code-completion tracker. Closing those 12 gaps was necessary, not sufficient. This document is the honest picture of what runs, what only compiles, what fakes itself convincingly, and what cannot move real money.Read this BEFORE telling anyone DemozPay is ready for a bank rail.
Status tags
Six tags, brutal:
- LIVE — runtime-verified end-to-end against a real shape (real DB, real HTTP, real gRPC). Safe to depend on.
- PARTIAL — one layer real, downstream layer missing. Will appear to work in tests; will fall over in production.
- STUB — booting, looks alive on
docker ps, does nothing useful. - PLANNED — proto/design/schema only.
- BLOCKED — depends on something missing; cannot be Live until that lands.
- DANGEROUS — implemented in a way that creates incident risk if shipped as-is. These are the things that wake up the on-call at 3 AM.
Headline picture
DemozPay is a competent skeleton with a working bank rail and three open doors.
- The bank-orchestrator transition (S1–S4, GAP-01..12) is structurally complete. The ledger has pending/posted lifecycle, the gateway speaks Dashen's HMAC shape, the webhook + poller close the loop, reconciliation produces signed drift. This is real.
- But authorization, RBAC, secrets, payroll, and four out of five frontends do not exist or are not enforced. The platform can move money correctly between mocked actors. It cannot yet verify that the actor invoking a disbursement is authorized to do so, nor that the money lands where the employer told us it should.
- And five operational systems that fintech CTOs assume are present — rate limiting, intrusion alerting, secret rotation, replica routing, regulator reporting — are absent.
A 7-day soak with zero drift on the bank rail is achievable. Opening that rail to real users today is not.
§1. Domain-by-domain reality
EWA — packages/ewa/backend/ + apps/api/src/products/ewa/
| Capability | Status | Notes |
|---|---|---|
| Domain model + 9-state lifecycle | LIVE | ewa-status.ts:32-61 covers PENDING → APPROVED → SUBMITTED_TO_BANK → ACCEPTED_BY_BANK → DISBURSED → REPAID terminal, with BANK_REJECTED + FAILED branches. |
RequestEwaUseCase | LIVE | packages/ewa/backend/application/request-ewa.usecase.ts — eligibility + idempotency + outbox. Unit-tested. |
DisburseEwaUseCase | LIVE | Pre-commit PENDING → gateway → ACCEPTED leaves PENDING; webhook/poller settles. |
EligibilityPolicy (accrued earnings - prior advances - fee) | PARTIAL | The math is real (eligibility.ts). The AccruedEarningsPort adapter is a placeholder reading Employee.baseSalary and pro-rating elapsed days. Real eligibility needs payroll calendar + pending deductions, which do not exist. |
| Repayment | LIVE (Phase B1, 2026-05-29) | RecordEwaRepaymentUseCase ships. Admin endpoint POST /api/ewa/requests/:id/record-repayment. Ledger DR PayrollClearing / CR ReceivableFromEmployee (P+F). Outbox event ewa.repaid.v1. 5 unit tests + idempotent. Payroll-event consumer path remains PLANNED until the payroll domain ships. |
| Cancel / Reject use cases | PLANNED | Domain methods exist (approve, reject); no application-level use case routes a reject. |
| Fee calculation | LIVE | feeFor() in eligibility.ts. Hardcoded via policy config (advance + fee bps). |
| Prisma repository | LIVE | apps/api/src/products/ewa/prisma-ewa.repository.ts — Prisma-backed findByProviderRef BYPASSes RLS by design. |
Verdict: EWA can submit and settle a bank transfer. It cannot collect repayment. Calling EWA Live is misleading until the payroll-deduction loop closes.
Lending — packages/lending/backend/ + apps/api/src/products/lending/
| Capability | Status | Notes |
|---|---|---|
| Domain model + lifecycle | LIVE | loan-status.ts:11-32; CLOSED, REJECTED, BANK_REJECTED, DEFAULTED terminals. |
QuoteLoanUseCase | LIVE | Schedule math + interest-rate policy. |
RequestLoanUseCase | LIVE | Underwriting (income multiple) + idempotency + outbox. Unit-tested. |
DisburseLoanUseCase | LIVE | Pre-commit PENDING, FI payable account, BANK_REJECTED reverses ledger. |
RecordRepaymentUseCase (S3.3 / GAP-10) | PARTIAL | The triple-entry math (DR PayrollClearing / CR ReceivableFromBorrower / CR InterestRevenue) is real. Caller is an admin endpoint, NOT a payroll consumer. A human operator must POST per installment; no payroll-cycle integration exists. |
RemitInstallmentToFiUseCase (B2 / GL-06) | LIVE (ledger side, Phase B2, 2026-05-29) | Admin endpoint POST /api/loans/:id/installments/:idx/remit-to-fi posts DR payable-to-fi-partner[fi_id] / CR payroll-clearing for the installment's principal. Outbox event loan.installment_remitted_to_fi.v1. Phantom-asset growth structurally closed on books. Bank-side outbound transfer is PLANNED — Phase B continuation requires schema change for employer payroll account modelling. |
| Installment auto-close | LIVE | Last installment → loan CLOSED + loan.closed.v1. |
| DEFAULTED handler | DANGEROUS | The enum value exists. No use case transitions a loan to DEFAULTED. No collections workflow. No write-off journal. Defaulted loans cannot be closed in the system today. |
| Interest accrual | PARTIAL | Interest is split equally across installments at schedule time. No accrual-by-time. Acceptable for flat-rate; misleading if anyone calls it "amortized". |
| Forbearance / restructure | PLANNED | Not modelled. |
Verdict: Lending can disburse and accept repayment if a human triggers each installment. Production lending needs a payroll-cycle consumer (Planned), a defaults handler (Planned), and a collections workflow (Planned).
BNPL
| Capability | Status |
|---|---|
packages/bnpl/ | PLANNED — directory does not exist. |
Legacy Prisma BNPLPurchase / BNPLPartner / Merchant models | DANGEROUS — Decimal(15,2) money fields violate ADR-005; no tenantId; no RLS. If anyone writes to these tables, they bypass every architectural invariant. |
| Merchant settlement flow | PLANNED |
| Repayment flow | PLANNED |
Verdict: Do not promise BNPL externally until the legacy models are deleted (not just deprecated) and a real packages/bnpl/ lands. The legacy models are an attractive nuisance: they will read as "BNPL exists" to any new engineer.
Payroll
| Capability | Status |
|---|---|
packages/payroll/ | PLANNED — directory does not exist. |
Legacy Payroll / PayrollEntry Prisma models | DANGEROUS — Decimal(15,2), no tenant_id, no RLS. Same attractive-nuisance shape as BNPL. |
| Payroll-run engine | PLANNED |
| Deduction calculator (drives EWA + lending repayment) | PLANNED |
| Pay-period / pay-cycle calendar | PLANNED |
Verdict: Payroll is the trust anchor of this entire platform's value proposition. Without payroll:
- EWA eligibility is fiction (we don't know what the employee earned).
- Lending repayment cannot happen automatically.
- BNPL repayment cannot happen automatically.
Saying "Payroll Q3" sounds reasonable. In product reality, payroll absence blocks 80% of the platform's value. This is the single largest gap on the page.
Savings / Equb
| Capability | Status |
|---|---|
packages/savings/ | PLANNED — does not exist. |
packages/equb/ | PLANNED — does not exist. |
Legacy Equb / EqubPayout / SavingGoal Prisma models | DANGEROUS — Decimal(15,2), no tenant_id, no RLS. |
Verdict: Same as BNPL. Roadmap items dressed as Live legacy models.
Integration Gateway — services/integration-gateway/
| Capability | Status | Notes |
|---|---|---|
| gRPC server boots + serves 4 RPCs | LIVE | cmd/integration-gateway/main.go. |
InitiateDisbursement | LIVE | Idempotent via (tenant_id, idempotency_key). State machine INITIATED → SUBMITTED → ACCEPTED → SETTLED. |
GetDisbursementStatus | LIVE | Polls partner adapter. |
LookupAccount | LIVE (existence-check, Phase C, 2026-05-29) / PARTIAL (name-match PLANNED) | Real RPC handler routes via AccountLookup interface to per-partner adapters. Dashen + mock both implement. Bank-sandbox backs E2E. EWA + Lending disburse use cases fail-closed on lookup-fail before any ledger / outbox / partner side effect. Audit row + 409 error with typed reason. 7/7 E2E pass. Name-match deferred — adapter returns resolved_holder_name but use case does not compare it against an expected name (no expected-name field in DTO/aggregate). See PHASE_C_LOOKUP_ACCOUNT.md. |
GetAdapterStatus | STUB | Always returns HEALTHY (lookup_and_health.go:48). No real adapter health tracking. |
| Webhook handler | LIVE | webhook/handler.go — HMAC-verified, max 1 MiB body. |
| Dashen adapter | LIVE | Real HTTP+HMAC against bank-sandbox. Production-shape. |
| Mock adapter | LIVE | Always ACCEPTED — for tests. |
| Other partner adapters (CBE, Awash, Telebirr, M-Birr) | PLANNED | |
| State machine | LIVE | DB function disbursement_transition_status. |
| Outbound retries | PARTIAL | Settlement poller retries on 30s tick; no exponential backoff; no jittered scheduling; no dead-letter. |
| Circuit breaker per partner | PLANNED | |
| Rate limiting per partner | PLANNED | |
| mTLS to partner | PLANNED — only HMAC today. | |
| BYPASSRLS role for cross-tenant poller | PLANNED — webhook/handler.go:62 carries the TODO. |
Verdict: Real partner-shape implementation against a real test bank. Missing account-verification (LookupAccount) is the single most operationally dangerous gap. A misrouted disbursement that the bank accepts is unrecoverable without partner cooperation.
Ledger — services/ledger/
| Capability | Status | Notes |
|---|---|---|
| Schema + invariants (balanced commit, append-only, partial-unique on reverses, RLS, FORCE RLS) | LIVE | Four migrations apply clean. |
PostTransaction w/ idempotency | LIVE | |
Reverse w/ double-reversal lockout | LIVE | |
GetBalance, GetEntries, ReconcileAccount | LIVE | |
ConfirmSettlement + MarkSettlementFailed (GAP-01) | LIVE | Idempotent; FailedPrecondition on illegal transitions. |
ReconcileWithBank (GAP-11b / S4.3) | LIVE | Signed drift, account-type sign rule, currency sanity. |
| Tenant isolation via SET LOCAL app.tenant_id | LIVE | LOCAL auto-resets at COMMIT/ROLLBACK (connection-leak safe). |
| Prometheus metrics on RPCs | LIVE (Phase D, 2026-05-29) | gRPC interceptor instruments every RPC with demozpay_ledger_rpc_requests_total{rpc,outcome} + demozpay_ledger_rpc_latency_seconds{rpc} histogram. Plus per-ledger gauges (entries posted, transaction-status transitions, reconcile drift). :50054/metrics. Cardinality-disciplined. |
| Snapshot/projection table | PLANNED — not yet needed at current scale. | |
| Multi-currency settlement | LIVE at schema level | currency field on entry; only ETB tested. |
Verdict: The ledger is the most production-ready component. Add Prometheus metrics before opening any rail.
Settlement / Bank-orchestration glue — apps/api/src/money/integration/
| Capability | Status |
|---|---|
| Webhook controller + HMAC verifier | LIVE |
| Settlement poller | LIVE (S3.1) |
Cross-domain applier (BankSettlementApplier) | LIVE |
findByProviderRef cross-tenant lookup | LIVE — uses BYPASSRLS pattern. |
| Stale-pending alert (>24h) | PLANNED — counter exists, threshold + escalation not wired. |
| Webhook DLQ | PLANNED — failed webhook is logged then 500; no replay buffer. |
Verdict: The happy path closes. The unhappy paths (24h staleness, repeated webhook signature failures, malformed bodies) log but do not page anyone.
Reconciliation — services/integration-gateway/internal/reconciliation/ + ledger ReconcileWithBank
| Capability | Status |
|---|---|
| Dashen CSV ingester | LIVE |
| Matcher (amount + reference + value-date ± 24h) | LIVE |
Runner pipeline orchestrator | LIVE |
Store w/ (tenant_id, partner, partner_reference) idempotency | LIVE |
Ledger-side ReconcileWithBank RPC | LIVE |
| Daily-cadence cron / scheduled-job harness | PLANNED — there is no scheduled invoker. The runner can be called by a human or a test script; no production cron entry. |
| Drift dashboard | PLANNED — no Grafana board file in infra/. |
| Flagged-line operator workflow | PLANNED — no admin UI; no API endpoint to query flagged lines. |
| Statement-pull automation | PLANNED — bank_statement_line ingests files; no code fetches files from a partner SFTP/API. |
Verdict: The reconciliation primitive is real. The reconciliation system — daily cadence, operator workflow, dashboard, paging — is not. Drift-clean cannot be claimed until daily cadence runs unattended.
Authentication — apps/api/src/identity/auth/ (better-auth)
| Capability | Status |
|---|---|
| Email + password sign-up + sign-in | LIVE |
| Email verification | PARTIAL — gated on NODE_ENV=production; dev skips. |
Organization plugin → activeOrganizationId → businessId | LIVE |
Phone OTP plugin (phoneNumber) | PARTIAL — wired; SMS sender is LoggingSmsSender (logs to stdout). No real SMS gateway in production code path. |
| 2FA / TOTP / WebAuthn | PLANNED — twoFactor table exists; plugin not wired. |
| Session storage | LIVE — Prisma Session table. |
Verdict: Email-only auth in production today. Bank-grade access requires MFA. Until phone OTP has a real SMS provider AND TOTP/WebAuthn is wired, no operator should hold admin credentials in production.
Authorization (AuthZ + RBAC) — apps/api/src/identity/auth/auth.guard.ts
| Capability | Status |
|---|---|
Global AuthGuard enforces req.user.id exists | LIVE |
@Public() opt-out | LIVE |
| RBAC (role → permission → endpoint) | LIVE (Phase A2, 2026-05-29) |
| Per-entity ownership checks (employee A disburses employee A's EWA, not employee B's) | PARTIAL (Phase A1+A2) |
| Business + Employee CRUD endpoints | LIVE (Phase A2, 2026-05-29) |
Verdict (updated 2026-05-29 post-Phase A): the authorization model is now LIVE for the 4 controllers in scope (Business, Employee, EWA, Lending). Tenant isolation at the DB layer (RLS) covers financial tables; application-layer RBAC + OrgRoleGuard + service-layer tenant-scoped queries cover identity-tier (Business, Employee). EWA/Lending self-service for borrowers (vs. admin-acting-for-employee) requires the Employee.userId schema link — Phase B item.
Observability — apps/api/src/_infra/observability/
| Capability | Status |
|---|---|
| Prometheus metrics on API | LIVE |
| Prometheus metrics on Go services | LIVE (Phase C + D) — gateway: lookup_* metrics from Phase C at :50053/metrics. Ledger (Phase D): every RPC instrumented + custom ledger gauges at :50054/metrics. |
| OpenTelemetry tracing wiring | LIVE — but no-op unless OTEL_EXPORTER_OTLP_ENDPOINT is set. No collector deployed. |
| Structured JSON logging | LIVE (Pino on API, slog on Go services) |
| PII redaction at logger | LIVE (Phase A3, 2026-05-29) |
| Request-ID propagation | PARTIAL — only via OTel trace_id, which is no-op without a collector. |
| Prometheus alert rules | PLANNED — no .yml rules anywhere in infra/. |
| Dashboards (Grafana) | PLANNED — none in repo. |
| SLOs / error budgets | PLANNED — no SLO file. |
Verdict: Metrics surface is healthy. Nothing alerts on them. Nobody is watching.
Outbox + Events — apps/api/src/_infra/outbox/
| Capability | Status |
|---|---|
Outbox publisher with FOR UPDATE SKIP LOCKED | LIVE |
| Idempotent Kafka producer | LIVE |
BYPASSRLS role gate via OUTBOX_DATABASE_URL | LIVE — but silently drains zero rows if env var not set in production. |
| Topic-per-bounded-context | LIVE |
| Event catalog doc | PLANNED — docs/architecture/event-catalog.md referenced in ADR-011 follow-ups; does not exist. |
| Consumers | PLANNED — services/notifications is /health only. No reconciliation consumer. No BI consumer. Events are produced into a void. |
Verdict: The outbox correctly persists + drains. Nothing consumes the events, so every domain emit is a tree falling in an empty forest. Notifications, reconciliation triggers, BI pipelines, audit-mirror — none exist.
Idempotency — apps/api/src/_infra/shared-infra/prisma-idempotency.store.ts
| Capability | Status |
|---|---|
| Two-phase claim (CLAIM → RECORD) | LIVE |
Composite PK (tenantId, scope, key) | LIVE |
Fingerprint-mismatch raises IdempotencyViolationError | LIVE |
| In-flight conflict → 409 | LIVE |
| Replay window TTL | DANGEROUS |
Verdict: Add a daily TTL cleanup job before go-live.
Audit — apps/api/src/_infra/shared-infra/prisma-audit.emitter.ts
| Capability | Status |
|---|---|
(action, entityType, entityId, before, after) recorded in same tx as state change | LIVE (ADR-008 enforced) |
actorId free-form string | LIVE — but trusts the x-actor-id header (see AuthZ findings above). Audit log is poisonable. |
| Immutable / append-only | LIVE — table has no UPDATE/DELETE triggers, but no application code mutates either. |
| Cross-tenant viewer for support | PLANNED |
Verdict: Audit log is real and poisonable. Fix x-actor-id validation before opening any rail.
Tenant isolation — apps/api/src/identity/tenant/tenant-context.middleware.ts + ADR-013
| Capability | Status |
|---|---|
| RLS on 5 financial tables (ewa_request, loan, outbox_event, idempotency_record, audit_entry) | LIVE |
| FORCE ROW LEVEL SECURITY | LIVE |
| Verify-guard DO block at migration time | LIVE |
loan_repayment table RLS | LIVE (S3 migration) |
bank_statement_line RLS | LIVE (S4 migration) |
disbursement + bank_event RLS (gateway DB) | LIVE |
| RLS on identity tables (Session, Account, Verification, User, Organization) | N/A by design — cross-tenant identity. |
| RLS on Business + Employee | N/A |
| Tenant context applied to Business + Employee controllers | DANGEROUS — not wired. See AuthZ. |
Verdict: Tenant isolation on financial rows is real and load-bearing. Tenant isolation on identity + workforce rows is not enforced and cannot be enforced by RLS alone because Business has no tenantId. Application-layer scoping is the only mechanism — and it isn't wired.
Frontends — apps/{admin,employer,employee,fi,merchant,docs}-web/
| App | Pages | Data source | Auth | Verdict |
|---|---|---|---|---|
admin-web | 9 routes | MOCK (10 mockData refs) | None wired | STUB |
employer-web | 8 routes | MOCK (43 mockData refs) | None wired | STUB |
employee-web | 9 routes | MOCK (5 refs) | localStorage only — no real backend session | DANGEROUS as a demo |
fi-web | 8 routes | API (no mocks detected) | Not validated end-to-end | PARTIAL |
merchant-web | 8 routes | API (no mocks detected) | Not validated end-to-end | PARTIAL |
docs-web | 1 page | Static | n/a | STUB |
Verdict: Four of six web apps cannot show real data to real users. The two that try (fi-web, merchant-web) have not been verified against the live API. Frontend integration is a 3-month effort across the suite.
Infrastructure / Ops
| Capability | Status |
|---|---|
| Docker-compose dev harness | LIVE |
Docker-compose test harness (docker-compose.test.yml) | LIVE — used by S2/S3/S4 verify scripts. |
| Kubernetes manifests | PLANNED — none. |
| Terraform / IaC | PLANNED — none. |
| Helm charts | PLANNED — none. |
| Secrets management (Vault / AWS SM / SOPS / sealed-secrets) | PLANNED — all secrets are env vars. |
| CI: lint + test + build + e2e via Nx | LIVE |
| CI: prisma-validate / migration-safety check | PLANNED |
| CI: SAST / secret scanning | PLANNED |
| Blue-green / canary deploy strategy | PLANNED |
| Database backup + PITR | PLANNED |
| Database replica routing | PLANNED |
| pgbouncer / connection pooler | PLANNED — direct pgxpool to a single primary. |
Verdict: Local dev is solid. There is no production deployment story. Going from docker-compose.yml to "regulator-acceptable production" is a 6-week dedicated workstream.
Compliance + Regulatory
| Capability | Status |
|---|---|
| ADR-014 ("DemozPay is orchestrator, not custodian") | PLANNED — recommended in SYSTEM_GAP_ACTION_PLAN.md but not written. |
| NBE (National Bank of Ethiopia) engagement | PLANNED |
| AML/CFT program | PLANNED |
| Sanctions screening (OFAC + Ethiopian sanctions list) | PLANNED |
| KYC + identity verification | PLANNED — packages/kyc/ does not exist. |
| Transaction monitoring (suspicious-pattern detection) | PLANNED |
| Regulatory reporting (monthly NBE returns, SAR/STR filings) | PLANNED |
| Data residency (Ethiopia-domiciled storage for PII) | PLANNED |
| Right-to-be-forgotten workflow | PLANNED |
| ISO 27001 / SOC 2 / PCI | PLANNED (per SECURITY_CONTROLS.md header table) |
Verdict: Outside the engineering remit, but every one of these items will be asked for by the first regulator conversation or the first partner-bank due-diligence. A real bank rail to real users without at least KYC + AML + sanctions is a regulator-incident waiting to be triggered.
§2. The five things that will hurt first in production
Ranked by likelihood × blast radius, not by code size.
Items 1 + 2 below were CLOSED by Phase A on 2026-05-29. They remain in the list (with strikethrough-equivalent annotation) so future-you reading the doc cold remembers what the urgent vectors WERE — and verifies they didn't regress.
-
Authorization bypass viaCLOSED (Phase A1).x-actor-id.TenantContextMiddlewarerejects the header with 400;actorIdsourced fromreq.user.idvia AsyncLocalStorage. Verified byapps/api/src/identity/tenant/tenant-context.middleware.spec.tsand full S3/S4 regression. -
Cross-tenant business/employee enumeration.CLOSED (Phase A2). Both controllers underTenantContextMiddleware;OrgRoleGuardrejects cross-tenant; service queries are tenant-scoped; bodybusinessIdmismatch → 403. -
EWA cannot be repaid.CLOSED (Phase B1).RecordEwaRepaymentUseCaseships with admin endpoint. Ledger journal correct; outbox event emitted. -
PARTIAL-CLOSED (Phase C, 2026-05-29). Existence-check ships LIVE: misrouted disbursement to a NON-EXISTENT account is now structurally impossible — the use case rejects at 409 before any money instruction. Name-match (different existing account) remains the carry-over Phase C continuation item. The blast radius is reduced from "unrecoverable" to "operator-actionable via bank-statement reconciliation".LookupAccountis a stub. -
The legacy
Wallet*+BNPL*+Payroll*+Equb*Prisma models still exist. They are@deprecatedand ESLint-blocked from imports, but the tables are still in the schema. They invite new engineers to write code that violates ADR-005/006/013. Delete the tables in a forward migration; don't leave the temptation.
§3. What is over-engineered
Be honest about this too.
packages/contracts/openapi/has a README listing future files. No source-of-truth OpenAPI spec exists for the REST endpoints we do ship. Either ship one or delete the placeholder.- OTel SDK wired at boot but no collector deployed. Pays cost (boot time, dependency tree) for zero current benefit. Either deploy a collector (Jaeger / Tempo / Grafana Cloud) or comment out until needed.
- Five separate web apps before the API is integrated with one. Premature breadth. Pick one (employer-web is the highest-value: it's the customer who pays) and integrate it end-to-end before moving to the next.
§4. What is under-engineered
- No payroll domain. Re-emphasising: payroll is the value proposition. Build it next.
- No KYC domain. Cannot ship in Ethiopia without identity-verification + sanctions-screening as Live, not Planned.
- No collections domain.
loan.markDefaulted()exists in the enum and has no caller. The first defaulting loan will reveal a process gap, not a code gap. - No admin tooling. Operator playbooks reference DB queries copy-pasted into psql. Every fintech needs an internal admin UI: replay a webhook, force-resync a disbursement, view a ledger account, replay an outbox event. None exist.
§5. The three most uncomfortable conversations
These need to happen before the next sprint:
- With Product: "We cannot promise BNPL, Savings, or Equb to partners in the next 90 days. They have no code." Adjust the roadmap, kill the bullet points, refund the customer expectations.
- With Security/Compliance: "We have one Live security control on the SECURITY_CONTROLS.md table — tenant isolation. The rest are Planned." Decide what is mandatory before pilot, not after.
- With the Board / Investors: "The 12 GAPs we marked closed represent code completeness, not platform readiness. We are 60–90 days from a real pilot, not 7 days." Re-set expectations now; the alternative is missing the date and explaining it later.
§6. What this document does NOT claim
- It does not claim the work to date was wasted. The S1–S4 work is necessary infrastructure and was done correctly.
- It does not propose a re-architecture. The boring-fintech-orchestrator model is right. The execution is incomplete.
- It does not list every TODO. It lists what would surprise a fintech CTO on day-one of taking the platform live.
§7. Cross-references
- Operational consequences of each gap →
PRODUCTION_READINESS.md. - Per-capability matrix (this doc's findings in tabular form) →
DOMAIN_COMPLETENESS_MATRIX.md. - What blocks go-live specifically →
GO_LIVE_BLOCKERS.md. - What we do about it in the next 90 days →
90_DAY_EXECUTION_PLAN.md.