firmflow. β Detailed Technical Architecture
Version: 1.2.0 | Last Updated: February 2026
Table of Contents
- System Overview
- Technology Stack
- Deployment Architecture
- Application Layer Architecture
- Database Architecture
- Authentication & Security Architecture
- AI Engine Architecture
- Storage Architecture
- API Surface Map
- Licensing System Architecture
- Email & Notification Architecture
- Payments Architecture
- Audit & Compliance Architecture
- Multi-Tenancy Architecture
- Frontend Component Architecture
- Key Workflows
- Environment & Configuration
- Observability & Monitoring
- Data Flow Diagrams
1. System Overview
firmflow. is a multi-tenant, on-premise-first document management and tax compliance platform purpose-built for Nigerian accounting and professional services firms. It is designed to operate entirely within a firm's own infrastructure (on-premise or self-hosted cloud VM), with an optional secure outbound-only hybrid AI bridge for intelligent processing.
Design Principles
| Principle |
Implementation |
| Data Sovereignty |
All documents stored on-premise; client data never leaves the licensed server |
| Multi-Tenancy |
Every database row is scoped to a firmId; no cross-firm data leakage possible |
| Hybrid AI |
AI runs via an outbound-only bridge; only anonymized metadata sent to Google |
| Immutable Audit Trail |
Cryptographically chained SHA-256 audit log resistant to tampering |
| License-Gated |
HMAC-signed license keys control access, edition features, and seat limits |
| Nigerian Compliance |
FIRS, LIRS, SCUML, NDPA, CAC, ICAN, ANAN standards embedded throughout |
2. Technology Stack
Runtime & Framework
| Layer |
Technology |
Version |
Rationale |
| Framework |
Next.js |
15.x |
App Router; SSR + API routes in a single deployable unit |
| Runtime |
Node.js |
20 LTS |
Required for fs, crypto, bcryptjs (no Edge Runtime) |
| Language |
TypeScript |
5.x |
Strict mode; zero implicit any |
| Package Manager |
npm |
10.x |
Lock file committed |
Frontend
| Layer |
Technology |
Notes |
| UI Library |
React 18 |
Server Components + Client Components mixed model |
| Styling |
Tailwind CSS 4 |
Utility-first; no CSS-in-JS runtime |
| Component Kit |
shadcn/ui |
Radix UI primitives; fully owned source, no vendor lock |
| Icons |
lucide-react |
Tree-shaken SVG icons |
| State |
React useState/useEffect |
No global state library; server-first mindset |
| Auth Client |
next-auth/react |
useSession, signOut |
| Fonts |
Inter (System Fallback) |
Loaded via next/font |
Backend & Data
| Layer |
Technology |
Notes |
| ORM |
Prisma |
v5.22 β schema-first, type-safe queries |
| Database |
PostgreSQL |
Neon (cloud) or self-hosted; any Postgres 14+ |
| Auth |
Auth.js v5 (NextAuth) |
Credentials + LDAP + MFA; DB sessions via Prisma adapter |
| File Storage |
Local FS / S3 / NAS |
Pluggable via StorageDriver interface |
| Encryption |
AES-256-GCM |
All stored files encrypted at rest on any driver |
| Email |
Resend |
Transactional emails; console fallback in dev |
| Payments |
Paystack |
Nigerian market; Kobo-denominated transactions |
| Push Notifications |
Firebase Cloud Messaging (FCM) |
Mobile push to client portal users |
AI Engine
| Component |
Technology |
Notes |
| Orchestration |
Genkit |
Google's AI flow orchestration framework |
| Model |
Gemini 2.5 Flash |
googleai/gemini-2.5-flash β fastest, cheapest Gemini model |
| Schemas |
Zod |
Input/output schema validation for all AI flows |
Infrastructure
| Component |
Technology |
Notes |
| Containerisation |
Docker |
Multi-stage build (node:20-alpine); standalone Next.js output |
| Orchestration |
Docker Compose |
Full stack: app + Postgres + volume mounts |
| CI |
GitHub Actions |
TypeCheck β Lint β Test β Build on every PR |
| Monitoring |
Instrumentation hook |
Sentry DSN stub; env validation on startup |
3. Deployment Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β On-Premise Deployment β
β β
β ββββββββββββββββ ββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β β Docker Host (firm server) β β
β β Reverse β β β β
β β Proxy ββββΆβ βββββββββββββββ βββββββββββββββββββββ β β
β β (Nginx / β β β Next.js β β PostgreSQL 14+ β β β
β β Caddy) β β β App βββββΆβ (Local or NAS) β β β
β β TLS 1.3 β β β Port 9002 β βββββββββββββββββββββ β β
β β β β β β β β
β ββββββββββββββββ β β β βββββββββββββββββββββ β β
β β β (Standaloneβ β Local FS / NAS β β β
β β β bundle) βββββΆβ Encrypted Vault β β β
β β βββββββββββββββ βββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Outbound-only
TLS 1.3 / HTTPS
βΌ
βββββββββββββββββββββββββββββββββ
β Google AI API (Gemini) β
β (metadata only β no docs) β
βββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββ
β Resend Email API β
βββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββ
β Paystack Payments API β
βββββββββββββββββββββββββββββββββ
Deployment Config (next.config.ts)
- Output:
standalone β ships only the minimal Node.js bundle needed to run; no node_modules bloat
- Security headers applied globally at the Next.js layer:
X-Frame-Options: DENY, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, Content-Security-Policy
- CORS restricted to
NEXT_PUBLIC_APP_URL on all /api/* routes (no wildcard)
4. Application Layer Architecture
src/
βββ app/ β Next.js App Router
β βββ (auth)/ β Unauthenticated routes (login, register)
β βββ dashboard/ β Protected staff application shell
β β βββ ai/ β AI Decision Lab
β β βββ audit-logs/ β Immutable audit viewer
β β βββ clients/ β Client management + onboarding wizard
β β βββ compliance/ β FIRS/LIRS compliance calendar
β β βββ documents/ β Document vault
β β βββ financials/ β Billing & revenue
β β βββ requests/ β Document request management
β β βββ resources/ β Knowledge resource library
β β βββ settings/ β Firm/user settings
β β βββ signatures/ β e-Signature management
β β βββ tax-prep/ β Tax prep pipeline (Kanban)
β βββ portal/ β Client portal (external-facing)
β βββ api/ β REST API routes (22 namespaces)
β βββ page.tsx β Marketing/landing page
β
βββ ai/
β βββ genkit.ts β Singleton Genkit AI client
β βββ flows/ β 14 named AI flows
β
βββ components/
β βββ dashboard/ β Staff app UI components
β βββ portal/ β Client portal components
β βββ pricing/ β Marketing page sections
β βββ features/ β Feature highlight components
β βββ ui/ β shadcn/ui primitives
β
βββ lib/ β Server-side utilities (20 modules)
βββ hooks/ β React custom hooks
βββ types/ β Global TypeScript types
βββ middleware.ts β Edge auth guard + rate limiter + security headers
βββ instrumentation.ts β Server startup validation
Request Lifecycle
Browser Request
β
βΌ
Next.js Middleware (middleware.ts)
βββ Rate limit check (in-memory Map, per IP)
βββ Public route allow-list check
βββ JWT session verification (auth())
βββ MFA enforcement gate
βββ Device-binding fingerprint check
βββ Role-based route guard (CLIENT β /portal, ADMIN β /dashboard/settings)
βββ Security header injection
β
βΌ
Next.js App Router
βββ Server Components (data fetching at render time)
βββ Client Components ('use client' β interactivity only)
βββ Route Handlers (API β JSON responses)
β
βΌ
Business Logic Layer (lib/)
βββ prisma.ts β PostgreSQL
βββ storage.ts β File I/O
βββ auth.ts β Identity
βββ ...
5. Database Architecture
Engine
PostgreSQL 14+ via Prisma ORM. Hosted on Neon in the cloud variant; any Postgres instance for on-premise.
Tenancy Model
firmId column-level isolation. Every model (except superAdmin-only) includes a firmId FK that Prisma enforces at the ORM layer. There are no row-level security policies at the DB level β isolation is 100% application-enforced via queries always scoped with where: { firmId }.
Full Entity Relationship Map
Firm βββ¬ββ User (staff)
β βββ Account (OAuth sessions)
β βββ Session
β βββ AuditLog entries
β βββ AIRequestLog entries
β
βββ Client βββ¬ββ Document
β βββ DocumentRequest β DocumentRequestItem
β βββ ComplianceTask β ComplianceTaskItem
β βββ SignatureRequest β SignatureRequestRecipient
β β βββ SignatureField
β β βββ SignatureEvent
β βββ RiskProfile (AI-generated)
β βββ ClientOnboarding (4-step workflow)
β βββ TaxPrepJob (tax prep pipeline)
β
βββ Document βββ¬ββ DocumentAnalysis (AI results)
β βββ ShareLink (expiring access)
β βββ DocumentApproval
β
βββ KnowledgeResource
βββ EngagementTemplate (ICAN letter templates)
βββ Integration (3rd-party software connectors)
βββ Notification
βββ License (HMAC-signed, edition-gated)
βββ Lead (marketing CRM)
βββ SyncNode β SyncChange (multi-branch sync)
Schema Design Decisions
| Decision |
Rationale |
BigInt for sizeBytes |
Files can exceed Int max (2.1 GB); JS handles BigInt via Prisma serialization |
Json? for metadata, settings, extractedData |
Avoids rigid schema for evolving AI outputs and user preferences |
@db.Text for large strings |
Prevents 255-char truncation in Postgres for notes, HTML bodies, base64 signatures |
@unique + cuid() for IDs |
Collision-free, URL-safe, no sequential guessing |
Chained hash on AuditLog |
hash + previousHash fields form a private Merkle-like chain |
@index on firmId, createdAt |
Supports the dominant query pattern: fetch-by-firm ordered by time |
Key Enums
UserRole: SUPER_ADMIN, FIRM_ADMIN, PARTNER, STAFF, CLIENT
ComplianceCategory: FIRS_CIT, FIRS_VAT, FIRS_WHT, LIRS_PAYE, LIRS_BIS, OTHER
TaxPrepType: 9 Nigerian tax types (CIT, VAT, WHT, CGT, EDT, Stamp Duty, PAYE, BIT, Personal)
TaxPrepStage: GATHERING β REVIEW β PREPARATION β PARTNER_REVIEW β CLIENT_APPROVAL β FILING β ARCHIVED
OnboardingStepStatus: PENDING, IN_PROGRESS, COMPLETE, SKIPPED
IntegrationProvider: 12 providers including Tally (popular in Nigerian SME segment)
LicenseEdition: TRIAL, BASIC, PROFESSIONAL, ENTERPRISE
SignatureDocumentType: TAX_DOCUMENT, ENGAGEMENT_LETTER, LEGAL_DOCUMENT, CONTRACT, AGREEMENT, OTHER
6. Authentication and Security Architecture
Authentication Flow
User submits credentials
β
βΌ
NextAuth Credentials Provider
β
βββ Zod schema validation (email format, min 8-char password)
β
βββ [If LDAP_ENABLED=true]
β βββ LDAP bind against Active Directory (ldap.ts)
β βββ Success β passwordValid = true
β βββ Failure β fall through to local
β
βββ Local bcrypt compare (bcryptjs, cost factor 12)
β
βββ Database lookup (isActive check)
β
βββ Update lastLoginAt, create AuditLog entry
β
βββ Generate device fingerprint: btoa(userAgent + IP)
β
βββ Return JWT payload:
{ id, email, name, firmId, role, twoFactorEnabled, deviceId }
Multi-Factor Authentication (TOTP)
- Library:
otplib compatible TOTP
- Secret generation + QR code enrollment via
/api/settings/mfa
- Middleware gates: if
twoFactorEnabled && !mfaVerified β redirect to /login/mfa
- Session flag
mfaVerified set only after correct TOTP code submission
Device Binding
- Device fingerprint =
btoa(userAgent|ip) stored in JWT as deviceId
- Every authenticated request: middleware recomputes fingerprint, compares with
req.auth.user.deviceId
- Mismatch β redirect to
/login?error=SessionExpired (protects stolen JWT cookie reuse)
Rate Limiting (In-Process)
- Implemented in
middleware.ts using a Map<string, {count, resetAt}>
- Auth routes: 20 requests/IP/minute
- Signature/password routes: 5 requests/IP/minute
- β οΈ Note: In-memory only β does not persist across Node.js instances (acceptable for single-instance on-premise deployment; Redis recommended for multi-instance)
Applied at two layers:
1. next.config.ts: Global X-Frame-Options, X-Content-Type-Options, CSP, Referrer-Policy, Permissions-Policy
2. middleware.ts: Applied per-request to all authenticated responses (duplicate-defense)
Content Security Policy
default-src 'self'
script-src 'self' 'unsafe-inline' (no eval in prod)
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com
img-src 'self' blob: data: https://placehold.co
font-src 'self' data: https://fonts.gstatic.com
connect-src 'self' https://generativelanguage.googleapis.com https://fcm.googleapis.com
frame-ancestors 'none' (DENY in header + CSP)
upgrade-insecure-requests
RBAC Matrix
| Resource |
SUPER_ADMIN |
FIRM_ADMIN |
PARTNER |
STAFF |
CLIENT |
| All Firms Admin |
β
|
β |
β |
β |
β |
| Firm Settings |
β
|
β
|
β |
β |
β |
| Team Management |
β
|
β
|
β |
β |
β |
| License Management |
β
|
β
|
β |
β |
β |
| Documents (Upload/View) |
β
|
β
|
β
|
β
|
Portal only |
| Clients |
β
|
β
|
β
|
β
|
β |
| AI Features |
β
|
β
|
β
|
β
|
β |
| Dashboard |
β
|
β
|
β
|
β
|
β |
| Client Portal |
β |
β |
β |
β |
β
|
7. AI Engine Architecture
Genkit Configuration
// src/ai/genkit.ts
export const ai = genkit({
plugins: [googleAI()],
model: 'googleai/gemini-2.5-flash', // default model
});
AI Flow Inventory (14 Flows)
| Flow |
Trigger |
Input |
Output |
Nigerian Context |
smartRequestAIDocumentGenerationFlow |
Manual |
Client info + purpose |
Document checklist |
FIRS/LIRS filing context |
documentAnalysisAgentFlow |
POST /api/documents/[id]/analyze |
File base64 + metadata |
Summary, risks, compliance score, extracted data |
Nigerian accounting standards |
checklistAutoSolverFlow |
POST compliance/tasks/[id]/auto-solve |
Task + checklist items |
Solved items, evidence notes |
FIRS penalty framework |
complianceTaskChecklistFlow |
Compliance scheduler |
Tax type + period |
Checklist template |
Nigerian tax calendar |
clientRiskAnalysisFlow |
Client risk assessment |
Client profile |
Risk score, risk factors |
CBN/AML risk criteria |
clientEmailDraftFlow |
Manual |
Client + purpose |
Professional email draft |
Formal Nigerian business language |
taxEngineFlow |
Tax calculation |
Client data + tax type |
Tax computation |
FIRS tax tables 2024 |
taxFilingExecutionFlow |
Filing automation |
Tax data + entity |
Filing instructions |
FIRS e-filing guidance |
documentReviewFlow |
Document flagging |
Document content |
Review findings |
Nigerian compliance flags |
firmExplorerFlow |
AI Decision Lab |
Query |
Firm analytics |
Nigerian industry benchmarks |
firmPerformanceAuditFlow |
Firm audit |
Firm metrics |
Performance report |
Nigerian accounting firm KPIs |
industryBenchmarkFlow |
AI Decision Lab |
Industry + metrics |
Benchmark comparison |
Nigerian sector data |
portalFilingAgentFlow |
Client portal |
Filing type |
Filing guidance |
FIRS/LIRS portal instructions |
vaultChatFlow |
Document Q&A |
Document content + query |
Contextual answer |
β |
AI Data Privacy Architecture
Client Document (on-premise)
β
βΌ [API route reads file from local storage]
Document Content
β
βΌ [Transform to base64 for AI input]
Gemini API Request βββββββββββββββββββββββββββββββΆ Google Generative AI
(Metadata + content analysis)
β
βΌ
AI Response (JSON)
β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ [Store structured results in DocumentAnalysis table]
Firm's Local Database
β οΈ Important: The raw document content IS sent to Google AI in base64. The connection is TLS 1.3 encrypted. Firms using maximum NDPA compliance should review Google's data processing agreement before enabling document analysis on sensitive PHI documents.
8. Storage Architecture
Driver Interface
interface IStorageDriver {
save(buffer: Buffer, storagePath: string): Promise<void>;
read(storagePath: string): Promise<Buffer>;
readStream(storagePath: string): NodeJS.ReadableStream;
delete(storagePath: string): Promise<void>;
}
Available Drivers
| Driver |
Config (STORAGE_DRIVER) |
Use Case |
LocalStorageDriver |
local (default) |
Development, small on-premise installs |
LocalStorageDriver (NAS mount) |
nas |
Enterprise NAS; path from STORAGE_NAS_PATH |
S3StorageDriver |
s3 |
Cloud deployments (AWS S3 or S3-compatible) |
Encryption-at-Rest
- Algorithm: AES-256-GCM (authenticated encryption)
- Key:
STORAGE_ENCRYPTION_KEY (hex-encoded 32-byte key)
- Format stored:
[12-byte IV][16-byte auth tag][ciphertext]
- If no key set: files stored unencrypted (warning logged)
- Path traversal protection:
safePath() validates all paths stay within basePath
File Naming Strategy
{firmId}/{timestamp}-{sha256_prefix_16}{extension}
Example: cm7abc123/1708512000-a3f1b9c2d4e5f678.pdf
This prevents:
- Filename collisions (timestamp + hash)
- Direct guessing of file paths (hash component)
- Cross-firm access (firmId prefix as directory)
9. API Surface Map
22 API Namespaces (86+ endpoints)
| Namespace |
Routes |
Auth Required |
Admin Only |
/api/auth/* |
NextAuth handlers (signin, signout, session, CSRF) |
No |
No |
/api/health |
GET β system health check |
No |
No |
/api/contact |
POST β lead/demo request form |
No |
No |
/api/public/* |
Portal access, share link verify |
No |
No |
/api/clients |
CRUD clients, risk assessment |
Yes |
No |
/api/clients/[id]/onboarding |
GET/PATCH onboarding workflow |
Yes |
No |
/api/documents |
Upload, list, filter |
Yes |
No |
/api/documents/[id]/analyze |
GET/POST AI analysis |
Yes |
No |
/api/documents/[id]/view |
Stream file to browser |
Yes |
No |
/api/documents/[id]/download |
Bulk ZIP download |
Yes |
No |
/api/documents/[id]/extract |
Unzip document bundle |
Yes |
No |
/api/requests |
Document request CRUD |
Yes |
No |
/api/signatures |
e-Signature CRUD + signing flow |
Yes |
Partial |
/api/signatures/sign |
Public token-based signing |
No |
No |
/api/compliance/tasks |
Compliance task CRUD |
Yes |
No |
/api/compliance/tasks/[id]/auto-solve |
AI checklist solver |
Yes |
No |
/api/tax-prep |
Tax prep job CRUD |
Yes |
No |
/api/tax-prep/[id] |
Update stage, delete job |
Yes |
No |
/api/engagement-templates |
Letter template CRUD |
Yes |
Admin |
/api/integrations |
Software integration connector |
Yes |
Admin |
/api/resources |
Knowledge resource CRUD |
Yes |
Admin (write) |
/api/ai/smart-request |
SmartRequestAIβ’ |
Yes |
No |
/api/ai/vault-chat |
Document Q&A |
Yes |
No |
/api/settings/* |
Firm & user settings |
Yes |
Admin (firm) |
/api/team/* |
Team invitations |
Yes |
Admin |
/api/user/preferences |
User preference save |
Yes |
No |
/api/admin/firms |
Super-admin firm management |
Yes |
SUPER_ADMIN |
/api/admin/leads |
Enterprise lead CRM |
Yes |
SUPER_ADMIN |
/api/billing |
Paystack integration |
Yes |
Admin |
/api/portal/* |
Client portal endpoints |
Token/Session |
No |
/api/sync |
Multi-branch node sync |
Yes |
No |
/api/audit |
Chain integrity verification |
Yes |
Admin |
10. Licensing System Architecture
FF|{firmId}|{TIER}|{expiresAt}|{HMAC_SIGNATURE}
Example: FF|cm7abc123|PROFESSIONAL|2026-12-31|A3F1B9C2D4E5
Verification Flow
License Key provided
β
βΌ
Parse 5 segments (prefix|firmId|tier|expiresAt|sig)
β
βββ firmId must match request context
β
βββ Recompute HMAC-SHA256 over (prefix|firmId|tier|expiresAt)
β using LICENSE_SECRET (env var)
β
βββ Constant-time comparison (timingSafeEqual) β prevents timing attacks
β
βββ Check expiry date
β
βββ Return { valid: boolean, payload: LicensePayload }
Edition Feature Gates
| Feature |
TRIAL |
BASIC |
PROFESSIONAL |
ENTERPRISE |
| Max Seats |
3 |
5 |
25 |
Unlimited |
| SmartRequestAIβ’ |
Limited |
β |
β
|
β
|
| Advanced Audit |
β |
β |
β
|
β
|
| API Integration |
β |
β |
β |
β
|
| Hardware Lock |
β |
β |
Optional |
β
|
Node-Locking (Enterprise)
hardwareId field on License model
- Hardware fingerprint computed from MAC address + CPU ID at install time
- License validation checks
hardwareId matches runtime machine
11. Email and Notification Architecture
Transactional Email (Resend)
Implemented in src/lib/mailer.ts. All emails are HTML-templated with plaintext fallback.
| Email |
Trigger |
Template |
| Invitation |
Admin invites team member |
Magic link to accept-invite |
| Password Reset |
User requests reset |
Time-limited reset link (JWT) |
| Document Request |
SmartRequestAIβ’ request sent to client |
Branded document list |
| Signature Request |
e-Sign sent to recipient |
Unique signing link with access token |
| Signature Complete |
All recipients signed |
Completion notification |
| Welcome |
New firm registered |
Onboarding guide |
Dev Mode: No API key = emails console.log-ed (no external calls in local dev)
In-App Notifications
Notification model in PostgreSQL (real-time polling or future WebSocket upgrade)
- Types:
INFO, SUCCESS, WARNING, ERROR, URGENT
- Firm-scoped; optionally targeted to a specific
userId
Push Notifications (FCM)
src/lib/fcm.ts β Firebase Cloud Messaging
fcmToken stored on Client model
- Used for client portal mobile push (document request received, signature required)
12. Payments Architecture
Provider: Paystack (Primary β Nigerian Market)
- All amounts in Kobo (β¦1 = 100 Kobo) as required by Paystack API
- Webhook signature verification using
HMAC-SHA512
Payment Flows
License Purchase Flow:
User selects edition β POST /api/billing/initialize
β
βΌ initializeTransaction()
Paystack payment page (hosted)
β
βΌ User pays
Paystack webhook β POST /api/billing/webhook
β
βββ verifyWebhookSignature (HMAC-SHA512)
βββ verifyTransaction (confirm payment)
βββ Generate signed license key
βββ Activate license in DB
13. Audit and Compliance Architecture
Cryptographically Chained Audit Log
Every AuditLog entry in src/lib/audit.ts implements a blockchain-like hash chain:
Entry #1: { action: "user.login", previousHash: "000...000", hash: SHA256(content+prevHash) }
Entry #2: { action: "document.uploaded", previousHash: hash(Entry#1), hash: SHA256(content+prevHash) }
Entry #N: { previousHash: hash(Entry#N-1), hash: SHA256(content+prevHash) }
Tamper detection: Any modification to a historical entry breaks the chain. verifyAuditIntegrity(firmId) traverses all entries and re-computes every hash.
Audit Events Logged
| Category |
Events |
| Auth |
user.login, user.logout, user.mfa.enrolled, password.reset |
| Documents |
document.uploaded, document.viewed, document.downloaded, document.deleted, document.analyzed |
| Clients |
client.created, client.updated, onboarding.updated |
| Compliance |
task.created, task.completed, task.auto-solved |
| Signatures |
signature.sent, signature.signed, signature.declined, signature.completed |
| Tax Prep |
taxprep.created, taxprep.updated |
| Admin |
license.activated, team.member.invited, integration.connected |
Nigerian Regulatory Alignment
| Regulation |
Implementation |
| NDPA 2023 |
Data export (/api/user/export), retention policies (planned), access logs |
| FIRS e-Filing |
Compliance categories match FIRS taxonomy; tax deadlines tracked |
| SCUML AML/CFT |
SCUML screening step in ClientOnboarding |
| CAC Compliance |
rcNumber stored on Client; CAC verification in onboarding |
| ICAN Standards |
isIcanCompliant flag on EngagementTemplate; ICAN-specific letter categories |
| CBN KYC |
Structured KYC step with CAC, TIN, SCUML triple-verification |
14. Multi-Tenancy Architecture
Isolation Model: Shared Database, Shared Schema, Application-Level Isolation
PostgreSQL Instance
βββ public schema
βββ Firm table (one row per tenant)
βββ User table (firmId FK, indexed)
βββ Client table (firmId FK, indexed)
βββ Document table (firmId FK, indexed)
βββ ... (all tables have firmId)
Why this model?
- Simpler for on-premise deployment (single DB to backup and maintain)
- Sufficient isolation when all queries are scoped via ORM
- Easier to upgrade schema across all tenants simultaneously
Isolation guarantees:
1. All Prisma queries include where: { firmId: session.user.firmId } β enforced by pattern, not DB policy
2. Session JWT carries firmId β injected at login, cannot be forged without AUTH_SECRET
3. File storage partitioned by firmId/ directory prefix
4. SUPER_ADMIN is the only role that can query across firms (only in src/app/dashboard/admin/)
15. Frontend Component Architecture
Component Hierarchy
DashboardShell (layout.tsx)
βββ Sidebar (navItems, adminItems, superAdminItems)
β βββ Role-based conditional rendering
βββ Header (SmartRequestAIβ’ button, bell, user menu)
βββ <main> β Page content
Pages (Server Components β fetch data)
βββ Feature Components (Client Components β interactivity)
Key Client-Side Components (State-Bearing)
| Component |
State |
API Calls |
TaxPrepKanban |
jobs[], loading, creating |
GET/POST/PATCH /api/tax-prep |
ClientOnboardingWizard |
onboarding, client, notes |
GET/PATCH /api/clients/[id]/onboarding |
DocumentPreviewModal |
analysis, isLoading, error |
GET/POST /api/documents/[id]/analyze |
DataExtractor |
documents[], loading |
GET /api/documents, POST /api/documents/[id]/analyze |
UserPreferences |
settings |
PATCH /api/user/preferences |
FirmExplorer |
results, query |
POST /api/ai/... |
ComplianceScheduler |
taxType, month, year |
POST /api/compliance/tasks |
TeamSettings |
members[], inviteOpen |
POST /api/team/invite, PATCH /api/team/[id] |
Data Fetching Pattern
- Server Components: Fetch directly via Prisma (zero network hop β SSR)
- Client Components:
useEffect β fetch() β local useState
- No SWR/React Query: Intentional β reduces bundle size; most data is loaded once per page visit
16. Key Workflows
Client Onboarding (Nigerian-Adapted)
New Client Created
β
βΌ
Step 1: KYC & CAC Verification
βββ CAC Registration Number confirmed (Corporate Affairs Commission)
βββ FIRS Tax Identification Number validated
βββ SCUML AML/CFT screening completed
β
βΌ
Step 2: Engagement Letter
βββ Select from ICAN-compliant template library
βββ Customise with firm/client variables
βββ Send via e-Signature workflow
βββ Letter signed β stored in vault
β
βΌ
Step 3: Document Collection
βββ SmartRequestAIβ’ generates tailored document checklist
βββ Client portal request created
βββ Client uploads via branded portal
β
βΌ
Step 4: Risk Assessment (CBN AML/CFT)
βββ AI-powered client risk scoring
β
βΌ
Step 5: Client Activated β
Tax Prep Job Lifecycle
New TaxPrepJob created (GATHERING)
β
βΌ [Documents collected from client]
REVIEW β AI document analysis, risk flags checked
β
βΌ [Staff prepares return]
PREPARATION β Tax computation (taxEngineFlow)
β
βΌ [Partner reviews]
PARTNER_REVIEW β Senior sign-off
β
βΌ [Client sees final]
CLIENT_APPROVAL β Client signature requested
β
βΌ [Filed with FIRS/LIRS]
FILING β Submission recorded, deadline met
β
βΌ
ARCHIVED β
β Stored in compliance vault
Document Analysis Flow
Staff clicks "Analyze with AI" on document
β
βΌ [GET /api/documents/[id]/analyze]
βββ Existing analysis? β Return cached
βββ None β prompt "Run analysis"
β
βΌ [POST /api/documents/[id]/analyze]
Read file from storage (LocalDriver/S3/NAS)
Decrypt buffer (AES-256-GCM)
Convert to base64
β
βΌ
documentAnalysisAgentFlow (Gemini 2.5 Flash)
Returns: summary, documentType, complianceScore,
keyFindings, riskFlags, extractedData
β
βΌ
Store in DocumentAnalysis table
Create AuditLog entry
β
βΌ
Display in AI Insights tab of DocumentPreviewModal
17. Environment and Configuration
Required Environment Variables
| Variable |
Required |
Description |
DATABASE_URL |
β
|
PostgreSQL connection URL (pooled) |
DIRECT_URL |
β
|
Direct Postgres URL (for Prisma migrations/push) |
AUTH_SECRET |
β
|
JWT signing secret (openssl rand -base64 32) |
NEXTAUTH_URL |
β
|
Canonical application URL (e.g. https://firm.example.com) |
NEXT_PUBLIC_APP_URL |
β
|
Same as NEXTAUTH_URL (public-facing) |
GEMINI_API_KEY |
Recommended |
Google Gemini API key (AI features disabled without it) |
LICENSE_SECRET |
Required in prod |
HMAC secret for license signing (risk: forgery if missing) |
STORAGE_DRIVER |
Optional |
local (default) | s3 | nas |
STORAGE_LOCAL_PATH |
Optional |
Override default ./uploads path |
STORAGE_ENCRYPTION_KEY |
Recommended |
AES-256-GCM key (hex, 64 chars) β files unencrypted if missing |
RESEND_API_KEY |
Optional |
Transactional email (console-only if missing) |
EMAIL_FROM |
Optional |
Sender address (e.g. noreply@firmflow.co) |
PAYSTACK_SECRET_KEY |
Optional |
Payment processing (billing disabled if missing) |
AWS_ACCESS_KEY_ID |
If STORAGE_DRIVER=s3 |
S3 credentials |
AWS_SECRET_ACCESS_KEY |
If STORAGE_DRIVER=s3 |
S3 credentials |
STORAGE_S3_BUCKET |
If STORAGE_DRIVER=s3 |
S3 bucket name |
STORAGE_NAS_PATH |
If STORAGE_DRIVER=nas |
NAS mount path |
LDAP_URL |
Optional |
LDAP/AD integration (e.g. ldap://dc.firm.local) |
FCM_SERVER_KEY |
Optional |
Firebase push notifications |
Environment Validation
src/lib/env.ts validates all variables at startup via instrumentation.ts. Missing required vars throw and log loudly. Missing optional vars warn but allow startup.
18. Observability and Monitoring
Current State
| Concern |
Status |
Implementation |
| Error logging |
β
Basic |
console.error with context tags [Module] |
| Startup validation |
β
Active |
instrumentation.ts β validateEnv() |
| Audit trail |
β
Active |
Chained AuditLog in PostgreSQL |
| AI request logging |
β
Active |
AIRequestLog (latency, token counts, success/fail) |
| Health endpoint |
β
Active |
GET /api/health β DB connectivity + uptime |
| Sentry integration |
π‘ Stub |
instrumentation.ts has commented Sentry DSN hook |
| Load testing |
β
Done |
Artillery YAML config in load-test.yml |
Recommended Production Additions
- Sentry DSN β uncomment stub in
instrumentation.ts
- Prometheus metrics endpoint β integrate
prom-client
- Grafana dashboard β connect to Prometheus
- Log shipping β forward
console to Loki or Cloudwatch
19. Data Flow Diagrams
Authentication Data Flow
[Browser]
β POST /api/auth/callback/credentials
β { email, password }
βΌ
[Middleware] Rate limit check (5/min)
βΌ
[NextAuth] Credentials provider authorize()
βββ [LDAP] bind() attempt β success/fail
βββ [Prisma] user lookup β bcrypt.compare()
βΌ
[JWT] { id, firmId, role, twoFactorEnabled, deviceId }
βΌ
[Browser] HttpOnly cookie (AUTH_SESSION)
βΌ
[Middleware] Every subsequent request: auth() β session check β role check
Document Upload Data Flow
[Browser] POST /api/documents (multipart/form-data)
βΌ
[API Route]
βββ auth() β firmId
βββ multer/formData parse
βββ License check (seat & feature gate)
βββ saveFile(buffer, filename, firmId)
β
βΌ
[StorageDriver.save()]
βββ AES-256-GCM encrypt(buffer)
βββ Write to {firmId}/{timestamp}-{hash}.ext
βΌ
[Prisma] Document.create({ firmId, clientId, storagePath, ... })
βΌ
[AuditLog] "document.uploaded"
βΌ
[Browser] 201 { document }
AI Analysis Data Flow
[Browser] POST /api/documents/[id]/analyze
βΌ
[API] auth() β firmId check β document lookup
βΌ
[Storage] readFile(storagePath) β decrypt β Buffer
βΌ
[Genkit] documentAnalysisAgentFlow({
fileBase64: buffer.toString('base64'),
fileName, mimeType, documentId
})
βΌ
[Google Gemini 2.5 Flash] Analysis
βΌ
[Prisma] DocumentAnalysis.create({ summary, riskFlags, extractedData, complianceScore })
βΌ
[Browser] { summary, complianceScore, riskFlags, extractedData }
This document reflects the codebase state as of firmflow. v1.2.0 (February 2026). Architecture decisions are documented to support onboarding of new engineers, security audits, and enterprise procurement due diligence.
Maintainer: firmflow. Engineering
Classification: Internal Technical Reference