Skip to content

firmflow. β€” Detailed Technical Architecture

Version: 1.2.0 | Last Updated: February 2026


Table of Contents

  1. System Overview
  2. Technology Stack
  3. Deployment Architecture
  4. Application Layer Architecture
  5. Database Architecture
  6. Authentication & Security Architecture
  7. AI Engine Architecture
  8. Storage Architecture
  9. API Surface Map
  10. Licensing System Architecture
  11. Email & Notification Architecture
  12. Payments Architecture
  13. Audit & Compliance Architecture
  14. Multi-Tenancy Architecture
  15. Frontend Component Architecture
  16. Key Workflows
  17. Environment & Configuration
  18. Observability & Monitoring
  19. 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)

Security Headers

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

License Key Format

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
  • 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