The problem
The observed SME tax workflow was a manual sequence of data entry, PDF exports, portal submissions, and phone calls to confirm receipt. Every step was a potential error, and the process lacked a machine-readable audit trail. The timing observation is part of the private project record rather than a public benchmark.
The Nigerian Revenue Service (NRS) enforces filing deadlines strictly. Missing one means penalties. Getting one wrong means an audit trail that doesn't match the submission. The friction was not just time — it was regulatory risk sitting inside a manual process.
TaxBridge was built to make correctness a property of the system, not a consequence of a careful accountant.
What was built
A Turborepo monorepo with four surfaces:
- React Native / Expo SDK 54 mobile app — the primary filing interface for accountants in the field, offline-first with a SQLite sync queue
- Next.js 15 admin dashboard — multi-client management, filing history, audit trail viewer
- Fastify 5 API — submission orchestration, NRS DigiTax integration, BullMQ queue management
- Java 17 / Spring Boot 3 compliance engine — jurisdiction-specific tax computation (VAT, WHT, PIT, CIT, CGT) with compile-time rule validation
The monorepo enforces a single TypeScript schema across mobile, web, and API surfaces. A rule change in the compliance engine propagates to every client automatically at the type level.
Key decision: PostgreSQL RLS over application-layer tenant filtering
Chosen: PostgreSQL Row-Level Security at the database engine
Over: WHERE tenant_id = ? filtering in the application layer
Because: NRS audit scrutiny demands an inspectable tenant boundary. "We filter in the application" leaves correctness distributed across every query path. PostgreSQL RLS moves that policy to the database schema, where it can reject rows outside the active tenant scope even when an application query omits its tenant predicate. The public evidence therefore treats RLS as a database-enforced isolation boundary, not as a guarantee against every possible schema or policy misconfiguration.
Key decision: Idempotent BullMQ queues for NRS API rate limits
Chosen: BullMQ with stable job identifiers and pre-execution deduplication
Over: Synchronous NRS API calls with simple 429 retry
Because: Filing windows produce burst traffic against a rate-limited external API. A simple retry loop can re-send a request after an ambiguous mid-execution failure. Every BullMQ job therefore carries an idempotency key derived from the submission content. Before execution, the worker checks whether that key has already succeeded; completed work is not submitted again.
Key decision: Hash-chained audit trail as a data model property
Chosen: Cryptographically bound append-only audit records
Over: Timestamped log table with standard UPDATE access
Because: The NRS requires an immutable audit trail. A log that can be retroactively edited — by anyone with database access — is not an audit trail. Every audit event in TaxBridge is cryptographically bound to the previous event's hash. Any retroactive modification breaks the chain at the point of modification, detectable without access to the original data. Integrity is a structural property of the data model, not a policy enforced at the application layer.
Constraint
The NRS API rate limit becomes a hard constraint during filing deadlines, when concurrent submissions are highest. The system must queue burst traffic and make retries safe against duplicate filing operations.
Offline filing is a real user requirement. Accountants in the field work on spotty Nigerian mobile networks. Every submission must survive the mobile app being killed mid-flight.
Evidence record
- Workflow timing — an accountant-reported before/after observation exists in the private project record; no public benchmark artifact is published
- Calculation path — load testing and coverage reports are part of the private project suite and are available to verified employers
- Tenant boundary — PostgreSQL RLS policies make isolation inspectable at the database schema rather than dependent on every application query
- Durable replay path — the offline SQLite queue persists work across app termination; BullMQ replays on reconnect with idempotency intact
Lessons
Compliance-critical systems fail most often at the seam between external API rate limits and internal queue management. The naive assumption is that external APIs are reliable enough to call synchronously. They are not, especially not under load, especially not with enforcement windows. Building the queue before the first incident — not retrofitting it after — is the only approach that holds.
The test suite is treated as an evidence artifact for calculation rules and submission state transitions. Coverage is not presented publicly without the underlying report.
Status
Active build. NRS DigiTax integration is in progress. Source is available to verified employers on request.