Skip to main content

CFS-DB-001 Database Architecture Overview

INTERNAL CONFIDENTIAL — AZ1.AI Inc. — Authorized Personnel Only This document contains proprietary and confidential information about the CODITECT Financial Suite database architecture. Unauthorized distribution, reproduction, or disclosure is strictly prohibited.


CFS-DB-001: Database Architecture Overview

Classification: INTERNAL CONFIDENTIAL Owner: Hal Casteel, CEO/CTO, AZ1.AI Inc. Document Code: CFS-DB-001 Last Updated: 2026-02-20 Component: CODITECT Financial Suite — General Ledger Engine Database: PostgreSQL 16+


Table of Contents

  1. Executive Summary
  2. Design Principles
  3. Entity-Relationship Diagram
  4. Table Summary
  5. Enum Types Reference
  6. Index Architecture
  7. Trigger & Business Logic Inventory
  8. Migration History
  9. Multi-Currency Architecture
  10. Audit & Compliance Architecture
  11. Naming Conventions
  12. Technology Stack
  13. Capacity & Performance Characteristics
  14. Related Documents

1. Executive Summary

The CODITECT Financial Suite General Ledger Engine is built on PostgreSQL 16+ with a shared-schema, shared-database multi-tenancy architecture enforced entirely through Row-Level Security (RLS). The database layer is designed to be the authoritative source of accounting truth: every business rule — double-entry balance, period state transitions, audit immutability, and tenant isolation — is enforced at the database level and cannot be bypassed by any application-layer bug, API misconfiguration, or network anomaly.

Current Implementation Snapshot

DimensionCurrent State
Core GL tables8
PostgreSQL enum types10
Applied migrations3 (001–003)
Database extensionsuuid-ossp, pgcrypto
Triggers12 (across all tables)
Stored functions4 (GL verification and balance management)
Audit log partitioningRange-partitioned by half-year
Multi-tenancy modelShared-schema + RLS
Currency slots2 (original + functional); target is 3 per IAS 21/ASC 830
Compliance targetsSPED ECD, SOX, IAS 21, ASC 830, GoBD

Architecture Decisions

PostgreSQL 16+ with RLS. The shared-schema, shared-database model was selected over database-per-tenant and schema-per-tenant alternatives after evaluating operational complexity, migration cost, and scaling characteristics at the anticipated tenant volume of hundreds to thousands of accounting firm clients. RLS enforcement occurs inside the PostgreSQL query executor — it cannot be bypassed by application code.

Database-level business rule enforcement. Double-entry balance verification, period state machine enforcement, audit immutability, and inactive account protections are implemented as PostgreSQL triggers. This means the database is self-defending: a direct SQL connection bypassing the application layer still cannot produce an invalid accounting state.

Partitioned audit log. The gl_audit_log table is range-partitioned by created_at in half-year intervals. This provides sub-second audit query performance over multi-year datasets while enabling data lifecycle management (archiving or dropping old partitions) without full table locks or data movement.

Materialized balance cache. Account balances are maintained in gl_account_balances via incremental upsert triggered on every journal line INSERT. A full-recompute repair function (gl_recompute_balances) allows correction of any balance discrepancy without data loss.

Multi-currency two-slot model (current). Journal lines carry original_amount/original_currency (transaction currency) and debit_amount/credit_amount (functional currency). The three-slot model required by IAS 21 and ASC 830 — adding a reporting currency — is planned for Phase 1 of the migration roadmap (CFS-DB-005).


2. Design Principles

These six principles govern every schema design decision in the GL Engine database layer. They are non-negotiable and must be preserved through all future migrations.

2.1 Tenant Isolation

Every table carries tenant_id UUID NOT NULL as the first non-primary-key column. This column is not defaulted — the application must supply it explicitly for every INSERT. PostgreSQL RLS policies enforce that every SELECT, INSERT, UPDATE, and DELETE operates only on rows matching the current session's tenant context, set via SET LOCAL app.current_tenant = '{uuid}'.

Tenant isolation is hardware-enforced inside the PostgreSQL query executor. It cannot be disabled by application code, bypassed by an ORM, or omitted by a developer who forgot to add a WHERE clause.

2.2 Double-Entry Enforcement

The trg_enforce_double_entry trigger fires on AFTER INSERT OR UPDATE on gl_journal_entries. It verifies that the sum of debit amounts equals the sum of credit amounts across all lines for a given journal entry at the moment of status change to POSTED. Entries that fail the balance check are rejected with error code GL001.

This is enforced at the statement level (not row level), ensuring that partial inserts during a multi-line journal entry do not trigger false positives.

2.3 Audit Immutability

The gl_audit_log table is append-only. The trg_prevent_audit_mutation trigger fires on BEFORE UPDATE OR DELETE on gl_audit_log and raises an exception unconditionally. No application role has UPDATE or DELETE privileges on this table. The audit record of every financial event is permanent and unalterable from within the system.

2.4 Period State Machine

Fiscal periods follow a strict four-state progression: FUTURE → OPEN → CLOSED → LOCKED. State transitions are enforced by the trg_period_state_machine trigger, which rejects any attempt to move a period to a non-adjacent state (e.g., FUTURE directly to LOCKED) or to reverse a terminal state (e.g., LOCKED back to CLOSED). Journal entries cannot be posted into a CLOSED or LOCKED period.

2.5 Metadata Extensibility

Every GL table carries a metadata JSONB column. This column provides a structured escape hatch for jurisdiction-specific, customer-specific, or future-state data requirements that do not yet warrant a dedicated column. Using JSONB for extension data prevents schema proliferation while preserving queryability via PostgreSQL's native JSONB operators and GIN indexing.

2.6 Balance Materialization

Account balances are not computed on the fly. The trg_update_balances trigger fires on every journal line INSERT and performs an incremental upsert into gl_account_balances. This means trial balance and balance sheet queries execute in milliseconds regardless of journal line volume, because they read from the pre-aggregated balance table rather than summing raw journal lines.

The gl_recompute_balances stored function provides a full recompute capability for recovery scenarios, rebuilding the balance cache from the canonical journal line source of truth.


3. Entity-Relationship Diagram

The following diagram represents the eight core GL tables and their primary relationships.

gl_accounts
┌────────────────────────────────────────────────────────────────────┐
│ id (PK) │
│ tenant_id (NOT NULL) ◄─────────────────────────────────────────┐ │
│ parent_id (FK → gl_accounts.id, self-referential hierarchy) │ │
│ account_code, account_type (enum), normal_balance (enum) │ │
│ account_status (enum), sped_account_code, currency │ │
│ name_pt, name_en, name_es, metadata JSONB │ │
└────────────────────────────────────────────────────────────────────┘
│ 1 │ 1
│ has many │ has many
▼ N ▼ N
gl_journal_lines gl_account_balances
┌────────────────────┐ ┌────────────────────────────────┐
│ id (PK) │ │ id (PK) │
│ tenant_id │ │ tenant_id │
│ journal_entry_id ──┼─┐ │ account_id (FK → gl_accounts) │
│ account_id ────────┼─┘ │ period_id (FK → gl_fiscal_...) │
│ line_number │ │ debit_total, credit_total │
│ debit_amount │ │ net_balance │
│ credit_amount │ │ last_updated_at │
│ original_amount │ └────────────────────────────────┘
│ original_currency │
│ description │ ▲ N
│ dimension_1..5 │ │ period reference
│ metadata JSONB │ │
└────────────────────┘ gl_fiscal_periods
▲ N ┌────────────────────────────────┐
│ contains many │ id (PK) │
│ │ tenant_id │
gl_journal_entries │ fiscal_year, period_number │
┌────────────────────────────────┐ │ start_date, end_date │
│ id (PK) │ │ period_state (enum) │
│ tenant_id │ │ closed_at, locked_at │
│ period_id (FK → gl_fiscal_...) │ │ metadata JSONB │
│ entry_date, entry_number │ └────────────────────────────────┘
│ reference, description │
│ entry_status (enum) │
│ module_source (enum) │
│ reversal_of_id (FK → self) │ gl_exchange_rates
│ reversed_by_id (FK → self) │ ┌────────────────────────────────┐
│ posted_at, posted_by │ │ id (PK) │
│ metadata JSONB │ │ tenant_id │
└────────────────────────────────┘ │ from_currency, to_currency │
│ rate_type (enum) │
│ rate_source (enum) │
│ rate, effective_date │
│ metadata JSONB │
└────────────────────────────────┘

gl_audit_log (PARTITIONED BY RANGE on created_at)
┌──────────────────────────────────────────────────────────────────────────┐
│ id (PK) │ tenant_id │ table_name │ record_id │ action (enum) │
│ old_data JSONB │ new_data JSONB │ changed_by │ created_at │
└──────────────────────────────────────────────────────────────────────────┘
Partition: gl_audit_log_2025_h1 (2025-01-01 .. 2025-07-01)
Partition: gl_audit_log_2025_h2 (2025-07-01 .. 2026-01-01)
Partition: gl_audit_log_2026_h1 (2026-01-01 .. 2026-07-01)
[future partitions created programmatically]

gl_sped_exports
┌────────────────────────────────────────────────────────────────────┐
│ id (PK) │ tenant_id │ export_type │ fiscal_year, period │
│ generated_at │ generated_by │ file_path │ checksum │
│ status │ metadata JSONB │
└────────────────────────────────────────────────────────────────────┘

3.1 Relationship Summary

RelationshipTypeConstraint
gl_accounts.parent_idgl_accounts.idSelf-referentialOptional (root accounts have NULL parent)
gl_journal_entries.period_idgl_fiscal_periods.idMany-to-oneNOT NULL
gl_journal_entries.reversal_of_idgl_journal_entries.idSelf-referentialOptional (reversal linkage)
gl_journal_entries.reversed_by_idgl_journal_entries.idSelf-referentialOptional (back-reference)
gl_journal_lines.journal_entry_idgl_journal_entries.idMany-to-oneNOT NULL, CASCADE DELETE
gl_journal_lines.account_idgl_accounts.idMany-to-oneNOT NULL
gl_account_balances.account_idgl_accounts.idMany-to-oneNOT NULL
gl_account_balances.period_idgl_fiscal_periods.idMany-to-oneNOT NULL
gl_audit_logAppend-only, no FKTrigger-populated from all tables
gl_sped_exportsIndependent per tenantReferences period by fiscal_year/period columns
gl_exchange_ratesIndependent per tenantReferenced by application layer during posting

4. Table Summary

TableRow Count (Seed)PurposePrimary IndexesRLS Enabled
gl_accounts~150Chart of Accounts with self-referential hierarchy, SPED mapping, tri-lingual names(tenant_id, account_code) UNIQUE; (tenant_id, parent_id); (tenant_id, account_type, account_status)Yes
gl_fiscal_periods~12Period management with state machine enforcement (FUTURE/OPEN/CLOSED/LOCKED)(tenant_id, fiscal_year, period_number) UNIQUE; (tenant_id, period_state)Yes
gl_journal_entries~50Journal entry headers with status workflow and reversal linkage(tenant_id, entry_date, entry_number) UNIQUE; (tenant_id, period_id, entry_status)Yes
gl_journal_lines~200Individual debit/credit lines, 2-slot currency, up to 5 dimensions(tenant_id, journal_entry_id, line_number) UNIQUE; (tenant_id, account_id)Yes
gl_account_balances~150Materialized balance cache, upserted by trigger on each journal line insert(tenant_id, account_id, period_id) UNIQUE; (tenant_id, period_id)Yes
gl_exchange_rates~30FX rates by type (SPOT/AVERAGE/CLOSING) and source, per effective date(tenant_id, from_currency, to_currency, rate_type, effective_date) UNIQUEYes
gl_audit_log~500Immutable audit trail for all GL mutations; range-partitioned by created_at(tenant_id, table_name, record_id) on each partition; (tenant_id, created_at)Yes
gl_sped_exports~5SPED ECD/ECF/EFD export job tracking with checksum and file path(tenant_id, export_type, fiscal_year, period) UNIQUEYes

5. Enum Types Reference

PostgreSQL native ENUM types are used for all columns with a bounded domain of values. Enums enforce at the database level that no invalid value can be stored regardless of application-layer validation.

5.1 gl_account_type

Classifies an account within the standard accounting equation.

ValueDescriptionNormal Balance
ASSETResources owned or controlledDEBIT
LIABILITYObligations owed to creditorsCREDIT
EQUITYOwner's residual interestCREDIT
REVENUEIncome from operationsCREDIT
EXPENSECosts incurred in operationsDEBIT

5.2 gl_normal_balance

Defines which side of the ledger increases the account. Used in balance reporting to correctly sign net balances.

ValueDescription
DEBITAccount increases with debits (Assets, Expenses)
CREDITAccount increases with credits (Liabilities, Equity, Revenue)

5.3 gl_account_status

Controls whether an account is available for posting.

ValueDescriptionPosting Allowed
ACTIVENormal operational stateYes
INACTIVESoft-disabled; preserved for historyNo (trigger enforced)
BLOCKEDHard-blocked pending review or compliance actionNo (trigger enforced)

5.4 gl_period_state

Implements the fiscal period state machine. Transitions are enforced by trg_period_state_machine and trg_prevent_closed_period_entry.

ValueDescriptionJournal PostingState Transitions Allowed
FUTUREPeriod not yet openNo→ OPEN
OPENCurrent active periodYes→ CLOSED
CLOSEDPeriod closed; no new entriesNo→ LOCKED, → OPEN (reopen)
LOCKEDPermanently sealed for complianceNoNone (terminal state)

5.5 gl_entry_status

Tracks journal entry lifecycle from draft to reversal.

ValueDescriptionLines Mutable
DRAFTWork in progress; not yet validatedYes
POSTEDValidated, balanced, and committedNo (trigger enforced)
REVERSEDSuperseded by a reversal entryNo

5.6 gl_module_source

Identifies the originating module that created a journal entry. Used for subledger reconciliation and financial close automation.

ValueDescription
BUDGETBudget allocation entries
FORECASTRolling forecast entries
MANUALHuman-entered journal entries
CONSOLIDATIONInter-entity consolidation entries
ADJUSTMENTAudit or correction adjustments
SYSTEMSystem-generated entries (e.g., period carry-forward, revaluation)

5.7 gl_rate_type

Classifies the economic measurement context of an exchange rate.

ValueDescriptionUsage
SPOTRate at a specific point in timeTransaction-date translation
AVERAGEPeriod-average rateIncome statement translation (IAS 21)
CLOSINGRate at period-endBalance sheet translation (IAS 21)

5.8 gl_rate_source

Records the provenance of an exchange rate for auditor traceability.

ValueDescription
MANUALEntered by a user; requires approval workflow in production
BCB_APIBrazilian Central Bank (Banco Central do Brasil) automated feed
BLOOMBERGBloomberg Terminal / Bloomberg Data License automated feed
SYSTEMSystem-computed (e.g., cross-rate derived from two base-currency rates)

5.9 gl_audit_action

Records the type of mutation event captured in the audit log.

ValueDescription
INSERTNew record created
UPDATEExisting record modified (field-level change captured in old_data/new_data)
STATUS_CHANGEStatus field changed (explicit capture for compliance workflows)

5.10 gl_sped_natura

Maps GL accounts to SPED (Brazilian tax authority) account nature codes for regulatory export compliance. These codes are defined by the Brazilian Receita Federal.

ValuePortuguese LabelDescription
01AtivoAsset accounts
02PassivoLiability accounts
03PLEquity (Patrimonio Liquido) accounts
04ResultadoIncome and expense accounts (P&L)
05CompensacaoCompensating accounts (off-balance sheet)
09OutrasOther accounts not fitting standard classification

6. Index Architecture

Indexes are designed around the two most common access patterns: tenant-scoped queries with account or period filters, and time-range queries for audit and reporting.

6.1 Primary Index Strategy

Every composite index begins with tenant_id as the leading key. This ensures PostgreSQL uses the index for all tenant-scoped queries, which represent 100% of production queries. Cross-tenant queries are an administrative function executed only by the gl_admin role and are not performance-critical.

-- Pattern: all GL indexes lead with tenant_id
CREATE UNIQUE INDEX uq_gl_accounts_tenant_code
ON gl_accounts (tenant_id, account_code);

CREATE INDEX idx_gl_journal_entries_tenant_period_status
ON gl_journal_entries (tenant_id, period_id, entry_status);

CREATE INDEX idx_gl_journal_lines_tenant_account
ON gl_journal_lines (tenant_id, account_id);

CREATE UNIQUE INDEX uq_gl_account_balances_tenant_account_period
ON gl_account_balances (tenant_id, account_id, period_id);

6.2 Audit Log Index Strategy

Partition-local indexes are created on each gl_audit_log partition automatically. Queries against the audit log specify tenant_id and a created_at date range, allowing PostgreSQL partition pruning to scan only the relevant partition.

-- Applied to each partition individually
CREATE INDEX idx_gl_audit_log_{period}_tenant_table
ON gl_audit_log_{period} (tenant_id, table_name, record_id);

CREATE INDEX idx_gl_audit_log_{period}_tenant_created
ON gl_audit_log_{period} (tenant_id, created_at);

6.3 Constraint Naming Convention

PrefixTypeExample
uq_UNIQUE constraintuq_gl_accounts_tenant_code
ck_CHECK constraintck_gl_journal_lines_nonzero
idx_Non-unique indexidx_gl_journal_entries_tenant_period
fk_Foreign keyfk_gl_journal_lines_account

7. Trigger & Business Logic Inventory

All business logic enforced at the database layer. No application-layer bypass is possible for any trigger-enforced rule.

Trigger NameTableEventPurpose
trg_enforce_double_entrygl_journal_entriesAFTER INSERT/UPDATEVerifies SUM(debits) = SUM(credits) across all lines on status → POSTED
trg_prevent_posted_modificationgl_journal_linesBEFORE UPDATE/DELETEBlocks modification of lines belonging to POSTED entries
trg_prevent_closed_period_entrygl_journal_entriesBEFORE INSERT/UPDATERejects entries into CLOSED or LOCKED periods
trg_check_period_on_postgl_journal_entriesBEFORE UPDATEVerifies period is OPEN at the moment of posting
trg_update_balancesgl_journal_linesAFTER INSERTIncremental upsert into gl_account_balances
trg_audit_journal_entriesgl_journal_entriesAFTER INSERT/UPDATEWrites audit record to gl_audit_log
trg_audit_journal_linesgl_journal_linesAFTER INSERT/UPDATEWrites audit record to gl_audit_log
trg_audit_accountsgl_accountsAFTER UPDATEWrites audit record to gl_audit_log
trg_prevent_audit_mutationgl_audit_logBEFORE UPDATE/DELETEUnconditionally raises exception; audit log is append-only
trg_period_state_machinegl_fiscal_periodsBEFORE UPDATEEnforces valid state transitions (FUTURE→OPEN→CLOSED→LOCKED)
trg_prevent_inactive_account_postinggl_journal_linesBEFORE INSERTRejects lines referencing INACTIVE or BLOCKED accounts
trg_updated_at_*All tablesBEFORE UPDATESets updated_at = NOW() on every mutation

7.1 Stored Function Inventory

FunctionReturnsPurpose
gl_verify_balances(tenant_id, period_id)TABLE(account_id, discrepancy)Identifies accounts where materialized balance diverges from SUM of journal lines
gl_get_trial_balance(tenant_id, period_id)TABLE(account, debit, credit)Returns trial balance view for a given tenant and period
gl_carry_forward_balances(tenant_id, from_period, to_period)voidCopies balance sheet account balances to open a new period
gl_recompute_balances(tenant_id, period_id)voidFull recompute of gl_account_balances from journal lines (repair function)

8. Migration History

8.1 Applied Migrations

MigrationFileDescriptionStatusApplied
001001_core_schema.sqlFoundation: all 8 tables, 10 enum types, all indexes, all triggers, partitioned audit log, RLS policiesAppliedPrototype phase
002002_account_name_translations.sqlAdds name_en and name_es columns to gl_accounts for tri-lingual Chart of Accounts (PT source, EN and ES translations)AppliedPrototype phase
003003_account_currency.sqlAdds currency VARCHAR(3) column to gl_accounts with default BRL; enables per-account currency designation for multi-currency environmentsAppliedPrototype phase

8.2 Pending Migrations (Roadmap)

Four additional phases are defined in CFS-DB-005. The following is a summary.

PhaseMigrationsKey ChangesPriority
Phase 1004–007Entity hierarchy (gl_entities), three-slot currency on journal lines, document_date columnP0 — Blocks commercial launch
Phase 2008–012Consolidation schema, RBAC tables, dimension master tables, period close checklistP1
Phase 3013–016CoA templates, regulatory metadata on periods, tax jurisdiction metadata on accountsP2
Phase 4017–020Hash partitioning for hot tables, approval workflow, intercompany partner trackingP2/P3

See CFS-DB-005 for full migration specifications, DDL statements, and rollback procedures.


9. Multi-Currency Architecture

9.1 Current Two-Slot Model

Journal lines currently carry two currency representations.

-- Current currency columns on gl_journal_lines
original_amount NUMERIC(20,6) NOT NULL, -- Amount in transaction currency
original_currency VARCHAR(3) NOT NULL, -- ISO 4217 currency code (e.g., USD, EUR)
debit_amount NUMERIC(20,6), -- Amount translated to functional currency
credit_amount NUMERIC(20,6), -- Amount translated to functional currency
-- Functional currency is set at the tenant level; currently defaulted to BRL

This model supports the common case: a transaction denominated in a foreign currency, translated to the entity's functional currency at the spot rate on the transaction date.

9.2 Target Three-Slot Model (Phase 1 Migration)

IAS 21 (The Effects of Changes in Foreign Exchange Rates), ASC 830 (Foreign Currency Matters), and CPC 02 R2 require three distinct currency measurements for each journal line.

SlotColumn NamesRate AppliedStandard
Transactiontransaction_amount, transaction_currencyNone (source)All standards
Functionalfunctional_debit, functional_creditSpot rate at transaction dateIAS 21, ASC 830
Reportingreporting_debit, reporting_creditClosing rate at period-endIAS 21, ASC 830

The three-slot model enables USD-denominated financial statements for entities whose functional currency is BRL, EUR, or another non-USD currency. This is a P0 requirement for multi-national commercial deployment.

9.3 Exchange Rate Table

gl_exchange_rates stores rates by type, source, and effective date. The unique constraint (tenant_id, from_currency, to_currency, rate_type, effective_date) ensures one canonical rate per currency pair per type per date. Rate corrections currently overwrite this record; version history is planned for Phase 4 (Migration 018).

-- Exchange rate lookup (application layer)
SELECT rate
FROM gl_exchange_rates
WHERE tenant_id = current_setting('app.current_tenant')::uuid
AND from_currency = 'USD'
AND to_currency = 'BRL'
AND rate_type = 'SPOT'
AND effective_date = $entry_date
LIMIT 1;

10. Audit & Compliance Architecture

10.1 Partition Strategy

The gl_audit_log table is declared as a partitioned parent table. Partitions are created in six-month intervals keyed on created_at. New partitions are created programmatically before the start of each new half-year period.

-- Parent table declaration
CREATE TABLE gl_audit_log (
id UUID NOT NULL DEFAULT uuid_generate_v4(),
tenant_id UUID NOT NULL,
table_name VARCHAR(100) NOT NULL,
record_id UUID NOT NULL,
action gl_audit_action NOT NULL,
old_data JSONB,
new_data JSONB,
changed_by UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
) PARTITION BY RANGE (created_at);

-- Half-year partitions
CREATE TABLE gl_audit_log_2026_h1
PARTITION OF gl_audit_log
FOR VALUES FROM ('2026-01-01') TO ('2026-07-01');

CREATE TABLE gl_audit_log_2026_h2
PARTITION OF gl_audit_log
FOR VALUES FROM ('2026-07-01') TO ('2027-01-01');

10.2 Audit Record Structure

Every gl_audit_log record captures the complete before and after state of a mutated row in JSONB. This enables field-level change analysis without additional instrumentation.

ColumnTypeDescription
table_nameVARCHARThe GL table that was mutated
record_idUUIDPrimary key of the mutated row
actiongl_audit_actionINSERT, UPDATE, or STATUS_CHANGE
old_dataJSONBComplete row state before mutation (NULL for INSERT)
new_dataJSONBComplete row state after mutation
changed_byUUIDUser UUID from application session context
created_atTIMESTAMPTZImmutable event timestamp (UTC)

10.3 Compliance Target Mapping

StandardRequirementImplementation
SPED ECDImmutable audit trail for Brazilian electronic accountinggl_audit_log append-only; gl_sped_exports job tracking
SOX Section 404Internal control evidence, audit trail retentiontrg_prevent_audit_mutation; 7-year partition retention policy
IAS 21 / ASC 830Three-slot currency on all journal linesPlanned Phase 1 migration (004_three_slot_currency.sql)
GoBDDocument date distinct from posting datePlanned Phase 1 migration (007_document_date.sql)
EU VATTax jurisdiction metadata on transactionsPlanned Phase 3 migration (013_tax_jurisdiction.sql)

10.4 SPED Export Workflow

The gl_sped_exports table tracks the generation of SPED ECD (electronic accounting records), ECF (corporate income tax), and EFD (digital tax bookkeeping) export files. Each export job record stores the file path, SHA-256 checksum, generation timestamp, and current status, enabling proof of regulatory submission for audit purposes.


11. Naming Conventions

Consistent naming across all GL schema objects ensures readability, tool compatibility, and predictable behavior for automated migration scripts.

11.1 Identifier Standards

ConventionRuleExample
Table prefixAll GL tables prefixed gl_gl_accounts, gl_journal_entries
Casesnake_case for all identifiersaccount_code, tenant_id
Primary keysUUID, generated by uuid_generate_v4()id UUID PRIMARY KEY DEFAULT uuid_generate_v4()
Tenant columnAlways tenant_id UUID NOT NULL, always first non-PK columntenant_id UUID NOT NULL REFERENCES tenants(id)
Timestampscreated_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()Set and maintained by trg_updated_at_* triggers
Audit columnscreated_by UUID, updated_by UUID (user UUID from session)Populated by application from JWT claims
EnumsPrefixed gl_, underscore-separated values in SCREAMING_SNAKEgl_period_state, value FUTURE

11.2 Constraint Naming

PrefixTypeExample
uq_UNIQUE constraint or UNIQUE INDEXuq_gl_accounts_tenant_code
ck_CHECK constraintck_gl_journal_lines_debit_or_credit
fk_FOREIGN KEY constraintfk_gl_journal_lines_entry
idx_Non-unique indexidx_gl_journal_entries_tenant_period_status
trg_Triggertrg_enforce_double_entry
fn_Stored function(used in application layer documentation)

11.3 Partition Naming

Audit log partitions follow the pattern gl_audit_log_{YYYY}_{h1|h2}:

gl_audit_log_2025_h1   -- 2025-01-01 to 2025-07-01
gl_audit_log_2025_h2 -- 2025-07-01 to 2026-01-01
gl_audit_log_2026_h1 -- 2026-01-01 to 2026-07-01

12. Technology Stack

12.1 Database Layer

ComponentSpecificationNotes
Database enginePostgreSQL 16+Minimum version enforced by trg_period_state_machine trigger syntax
Extension: uuid-osspuuid_generate_v4()All primary key generation
Extension: pgcryptocrypt(), gen_salt()Sensitive field hashing where applicable
Multi-tenancyShared-schema + RLSSET LOCAL app.current_tenant per transaction
PartitioningRange partitioning on created_atApplied to gl_audit_log; hot table hash partitioning planned Phase 4

12.2 Application Layer

ComponentSpecificationNotes
RuntimeNode.js 20 LTSTypeScript strict mode
PostgreSQL driverpg (node-postgres)Connection pool with tenant context injection middleware
ORM / query builderKnex.jsRaw SQL for trigger-critical paths; Knex for routine CRUD
MigrationsKnex migrationsSequential numbered SQL files; never destructive
Connection poolpg-poolMin 5, Max 20 per pod; PgBouncer in front for 10,000+ user scale

12.3 Tenant Context Injection Pattern

The Node.js connection pool wraps every acquired connection with tenant context injection before the SQL is executed.

// Middleware pattern: inject tenant context on every connection
async function withTenantContext<T>(
pool: Pool,
tenantId: string,
fn: (client: PoolClient) => Promise<T>
): Promise<T> {
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query(
"SELECT set_config('app.current_tenant', $1, true)",
[tenantId]
);
const result = await fn(client);
await client.query('COMMIT');
return result;
} catch (err) {
await client.query('ROLLBACK');
throw err;
} finally {
client.release();
}
}

The set_config(..., true) call with is_local = true mirrors SET LOCAL behavior: the configuration variable is cleared when the transaction ends, ensuring the connection pool cannot leak tenant context between requests.

12.4 Infrastructure

ComponentSpecification
DeploymentGoogle Kubernetes Engine (GKE); one PostgreSQL StatefulSet per environment
Database storageGCP Cloud SQL (PostgreSQL 16) for managed operations; direct StatefulSet for maximum performance
Backuppg_dump + Cloud Storage; point-in-time recovery via WAL archiving
Monitoringpg_stat_statements, pg_stat_activity; exported to Cloud Monitoring
Connection proxyCloud SQL Auth Proxy; PgBouncer in transaction pooling mode for scale

13. Capacity & Performance Characteristics

13.1 Current Prototype Scale

TableSeed Row CountTarget Production (Year 1)Target Production (Year 3)
gl_accounts~150500 per tenant × 1,000 tenants = 500,000500 per tenant × 10,000 tenants = 5M
gl_fiscal_periods~1212–24 per tenant12–24 per tenant
gl_journal_entries~5010,000 per tenant/year100,000 per tenant/year
gl_journal_lines~20050,000 per tenant/year500,000 per tenant/year
gl_account_balances~1506,000 per tenant/year60,000 per tenant/year
gl_audit_log~500200,000 per tenant/year2,000,000 per tenant/year

13.2 Performance Targets

Query TypeTarget P95 Response TimeMechanism
Trial balance retrieval< 50 msReads gl_account_balances (materialized)
Journal entry list (paginated, 50 rows)< 100 msComposite index on (tenant_id, period_id, entry_status)
Audit log query (30-day window)< 200 msPartition pruning + partition-local index
Journal entry POST (with balance check)< 300 msSynchronous trigger execution within transaction
Full balance recompute (repair)< 5 s per tenant per periodgl_recompute_balances() batch function

13.3 Scalability Path

The current schema supports single-node PostgreSQL with connection pooling to approximately 500 concurrent tenants. The following architectural steps are defined for scale-out:

Scale TierTenantsMechanism
Prototype1–50Single Cloud SQL instance, direct connections
Pilot50–500Cloud SQL + PgBouncer transaction pooling
Commercial500–5,000Cloud SQL read replicas for reporting queries; CQRS separation
Enterprise5,000+Hash partitioning of hot tables by tenant_id; read replica per shard; eventual Citus for distributed PostgreSQL

This document is the entry point to the GL Engine database documentation series. The following documents provide detailed specifications for specific subsystems.

Document CodeTitleDescription
CFS-DB-001Database Architecture OverviewThis document. System-level overview, ER diagram, enum reference, naming conventions
CFS-DB-002Core Schema ReferenceComplete DDL for all 8 tables, all 10 enum types, all constraints and indexes. Annotated SQL with design rationale
CFS-DB-003Security & Multi-Tenancy ModelRLS policy specifications, role permissions, tenant context injection, audit trail immutability
CFS-DB-004Business Logic LayerComplete trigger and stored function specifications with SQL source, error codes, and test procedures
CFS-DB-005Migration Roadmap & Target SchemaGap analysis (20 identified gaps), four-phase migration plan, target schema DDL, rollback procedures
CFS-004Technical ArchitectureSystem-level C4 architecture, microservices topology, API gateway design, infrastructure specification

Document Reading Order for New Engineering Team Members

  1. CFS-DB-001 (this document) — understand the overall structure, principles, and table inventory
  2. CFS-DB-003 — understand how tenant isolation and security are enforced
  3. CFS-DB-004 — understand all database-level business rules before writing application code
  4. CFS-DB-002 — reference for DDL details during implementation
  5. CFS-DB-005 — understand the migration roadmap before making any schema changes

Hal Casteel CEO/CTO, AZ1.AI Inc. Copyright 2026 AZ1.AI Inc. All rights reserved.

This document is subject to version control. Changes must be approved by the document owner and reflected in the document version history. The current version is 1.0.0, dated 2026-02-20.