Skip to main content

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

CFS-DB-002: Core Schema Reference

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


Table of Contents

  1. Overview
  2. Database Prerequisites
  3. Enum Types
  4. Table Reference
  5. Migration History
  6. Entity Relationship Summary
  7. Index Inventory
  8. Design Notes and Conventions
  9. Related Documents

1. Overview

This document is the authoritative schema reference for the CODITECT Financial Suite General Ledger Engine. It documents every table, column, constraint, index, and enum type in the GL Engine's PostgreSQL 16+ schema, including all changes introduced by the three migrations applied to date.

The GL Engine schema is designed around five architectural principles:

  1. Hard multi-tenancy isolation -- every table carries tenant_id; Row-Level Security is enforced at the database layer (see CFS-DB-003).
  2. Immutable audit trail -- the gl_audit_log table is append-only, range-partitioned, and protected by trigger-level mutation prevention.
  3. Double-entry enforcement -- debit/credit balance is enforced by database trigger, not application logic alone (see CFS-DB-004).
  4. Brazilian fiscal compliance -- SPED mapping fields, SPED export tracking, and the gl_sped_natura enum are first-class schema concerns.
  5. Multi-currency readiness -- every monetary line carries original currency, original amount, and exchange rate alongside functional-currency debit/credit columns.

Schema scope: 8 tables, 10 enum types, 3 migrations applied. The schema targets PostgreSQL 16 and relies on the uuid-ossp and pgcrypto extensions.


2. Database Prerequisites

The following PostgreSQL extensions must be enabled before applying migrations:

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
ExtensionPurpose
uuid-osspProvides uuid_generate_v4() used as the default primary key generator for all tables
pgcryptoAvailable for future encryption-at-rest patterns; required by the security layer

Minimum PostgreSQL version: 16.0

Required privileges for migration execution: SUPERUSER or a role with CREATE, TRIGGER, and REFERENCES privileges on the target schema.


3. Enum Types

All enum types reside in the GL schema namespace and are created by migration 001_core_schema.sql. They are referenced as column types across multiple tables.

3.1 gl_account_type

Purpose: Classifies every account in the Chart of Accounts according to the five standard accounting categories. Controls normal balance derivation and financial statement placement.

CREATE TYPE gl_account_type AS ENUM (
'ASSET', 'LIABILITY', 'EQUITY', 'REVENUE', 'EXPENSE'
);
ValueDescriptionNormal BalanceStatement
ASSETEconomic resources owned or controlledDEBITBalance Sheet
LIABILITYObligations owed to external partiesCREDITBalance Sheet
EQUITYResidual interest of ownersCREDITBalance Sheet
REVENUEIncome earned from operationsCREDITIncome Statement
EXPENSECosts incurred in generating revenueDEBITIncome Statement

Used by: gl_accounts.account_type


3.2 gl_normal_balance

Purpose: Records the side of the accounting equation on which an account normally carries a positive balance. Used for balance sign normalization in reporting.

CREATE TYPE gl_normal_balance AS ENUM ('DEBIT', 'CREDIT');
ValueDescription
DEBITAccount increases on the debit side (left); assets, expenses
CREDITAccount increases on the credit side (right); liabilities, equity, revenue

Used by: gl_accounts.normal_balance


3.3 gl_account_status

Purpose: Controls the operational state of a Chart of Accounts entry. Only ACTIVE accounts may receive journal line postings.

CREATE TYPE gl_account_status AS ENUM ('ACTIVE', 'INACTIVE', 'BLOCKED');
ValueDescriptionCan Receive Postings
ACTIVEAccount is in normal operational useYes
INACTIVEAccount is retired but retained for historical referenceNo
BLOCKEDAccount is administratively suspended; may be reactivatedNo

Used by: gl_accounts.status


3.4 gl_period_state

Purpose: Governs the lifecycle state of a fiscal period. Determines whether journal entries may be created, modified, or posted against the period.

CREATE TYPE gl_period_state AS ENUM ('FUTURE', 'OPEN', 'CLOSED', 'LOCKED');
ValueDescriptionAccepts New EntriesModification Allowed
FUTUREPeriod is defined but not yet activeNoYes (by admin)
OPENPeriod is active for postingYesYes
CLOSEDPeriod is closed; balances are finalNoNo
LOCKEDPeriod is locked for regulatory/audit purposes; immutableNoNo

State transition rules: FUTURE -> OPEN -> CLOSED -> LOCKED. Transitions are irreversible after CLOSED.

Used by: gl_fiscal_periods.state


3.5 gl_entry_status

Purpose: Tracks the lifecycle state of a journal entry header. Only POSTED entries contribute to account balances and appear in financial reports.

CREATE TYPE gl_entry_status AS ENUM ('DRAFT', 'POSTED', 'REVERSED');
ValueDescriptionAffects BalancesModifiable
DRAFTEntry is being prepared; not yet postedNoYes
POSTEDEntry has been approved and posted; contributes to balancesYesNo
REVERSEDEntry has been reversed by a subsequent correcting entryYes (net zero)No

Used by: gl_journal_entries.status


3.6 gl_module_source

Purpose: Identifies the originating subsystem or workflow that created a journal entry. Enables source-segregated reporting and audit filtering.

CREATE TYPE gl_module_source AS ENUM (
'BUDGET', 'FORECAST', 'MANUAL', 'CONSOLIDATION', 'ADJUSTMENT', 'SYSTEM'
);
ValueDescription
BUDGETEntry originated from the budget module
FORECASTEntry originated from the forecast or rolling estimate module
MANUALEntry was created manually by an authorized user
CONSOLIDATIONEntry was generated by the intercompany consolidation process
ADJUSTMENTEntry is a period-end or audit adjustment
SYSTEMEntry was generated by automated system processes (e.g., currency revaluation)

Used by: gl_journal_entries.module_source, gl_audit_log.module_source (TEXT column, not typed)


3.7 gl_rate_type

Purpose: Classifies the methodology used to determine an exchange rate. Different rate types are required for different accounting treatments.

CREATE TYPE gl_rate_type AS ENUM ('SPOT', 'AVERAGE', 'CLOSING');
ValueDescriptionTypical Usage
SPOTRate at a specific point in timeTransaction recording
AVERAGEArithmetic average rate over a periodIncome statement translation
CLOSINGRate at the close of a reporting periodBalance sheet translation

Used by: gl_exchange_rates.rate_type


3.8 gl_rate_source

Purpose: Records the data provenance of an exchange rate, supporting audit requirements and data quality controls.

CREATE TYPE gl_rate_source AS ENUM ('MANUAL', 'BCB_API', 'BLOOMBERG', 'SYSTEM');
ValueDescription
MANUALRate entered manually by an authorized user
BCB_APIRate sourced from Banco Central do Brasil API (ptax)
BLOOMBERGRate sourced from Bloomberg data feed
SYSTEMRate calculated or propagated by system process

Used by: gl_exchange_rates.source


3.9 gl_audit_action

Purpose: Classifies the type of change recorded in each audit log entry.

CREATE TYPE gl_audit_action AS ENUM ('INSERT', 'UPDATE', 'STATUS_CHANGE');
ValueDescription
INSERTA new record was created
UPDATEOne or more fields on an existing record were modified
STATUS_CHANGEThe status or state field of a record was changed (tracked separately for regulatory traceability)

Note: DELETE is not a valid action; the GL schema does not permit hard deletes on financial records. Soft deletion is handled via status transitions.

Used by: gl_audit_log.action


3.10 gl_sped_natura

Purpose: Maps a Chart of Accounts entry to a Brazilian SPED (Sistema Publico de Escrituracao Digital) account nature classification. Required for ECD (Escrituracao Contabil Digital) export generation.

CREATE TYPE gl_sped_natura AS ENUM (
'01', '02', '03', '04', '05', '09'
);
ValuePortuguese DescriptionEnglish Description
01AtivoAsset
02PassivoLiability
03Patrimonio LiquidoShareholders' Equity
04ResultadoIncome/Result
05CompensacaoCompensating/Contingency
09OutrasOther

Used by: gl_accounts.sped_natura

Regulatory reference: SPED ECD Leiaute, Registro I052, Campo "IND_CTA_EQTM_NAT_INF_COMPL"


4. Table Reference

4.1 gl_accounts -- Chart of Accounts

Purpose: The master Chart of Accounts registry. Stores every account available to a tenant for journaling, including hierarchical parent-child relationships, SPED compliance mapping, and tri-lingual name support.

Key characteristics:

  • Self-referential hierarchy via parent_id supports unlimited depth account trees
  • is_header flag distinguishes summary (non-postable) accounts from leaf (postable) accounts
  • SPED mapping fields (sped_natura, sped_cod_agl, sped_referencial_code) are maintained per-account for ECD export
  • Tri-lingual naming (account_name as Portuguese canonical, account_name_en, account_name_es) added by migration 002
  • Per-account currency column added by migration 003

Column Specification:

ColumnTypeNullableDefaultDescription
idUUIDNOT NULLuuid_generate_v4()Primary key
tenant_idUUIDNOT NULL--Tenant isolation key; must match session context
parent_idUUIDNULL--Self-referential FK to gl_accounts(id); NULL for root accounts
account_numberTEXTNOT NULL--Unique account code within tenant (e.g., 1.1.01.001)
account_nameTEXTNOT NULL--Canonical account name in Portuguese (primary language)
account_name_enTEXTNULL--English translation of account_name (migration 002)
account_name_esTEXTNULL--Spanish translation of account_name (migration 002)
account_typegl_account_typeNOT NULL--Account classification: ASSET, LIABILITY, EQUITY, REVENUE, EXPENSE
normal_balancegl_normal_balanceNOT NULL--Normal positive side: DEBIT or CREDIT
statusgl_account_statusNOT NULL'ACTIVE'Operational state: ACTIVE, INACTIVE, BLOCKED
levelINTEGERNOT NULL1Depth in account hierarchy; root = 1
is_headerBOOLEANNOT NULLFALSETRUE = summary account (non-postable); FALSE = detail account (postable)
currencyCHAR(3)NOT NULL'BRL'Functional currency for this account; ISO 4217 code (migration 003)
sped_naturagl_sped_naturaNULL--SPED ECD account nature classification (01-05, 09)
sped_cod_aglTEXTNULL--SPED aggregation code (Codigo de Aglutinacao)
sped_referencial_codeTEXTNULL--SPED referential plan code for ECD I052 register
descriptionTEXTNULL--Free-text description or notes for this account
metadataJSONBNULL'{}'Extensible key-value store for integration and UI-layer attributes
created_atTIMESTAMPTZNOT NULLNOW()Record creation timestamp
created_byUUIDNOT NULL--User UUID who created the record
updated_atTIMESTAMPTZNOT NULLNOW()Last modification timestamp; maintained by trigger
updated_byUUIDNOT NULL--User UUID who last modified the record

Constraints:

Constraint NameTypeDefinitionDescription
gl_accounts_pkeyPRIMARY KEY(id)Record identity
gl_accounts_parent_id_fkeyFOREIGN KEYparent_id REFERENCES gl_accounts(id)Hierarchical self-reference; ON DELETE is not set -- parent deletion requires child reassignment first
uq_account_number_tenantUNIQUE(tenant_id, account_number)Account numbers are unique per tenant
ck_account_level_positiveCHECKlevel >= 1Level must be a positive integer; root accounts are level 1

Indexes:

Index NameColumnsTypeDescription
idx_gl_accounts_tenant(tenant_id)B-treePrimary tenant filter; used by all tenant-scoped queries
idx_gl_accounts_parent(tenant_id, parent_id)B-treeHierarchy traversal; supports parent-child tree queries
idx_gl_accounts_type(tenant_id, account_type)B-treeAccount type filtering for trial balance and financial statements
idx_gl_accounts_sped(tenant_id, sped_natura)B-treeSPED export queries filtering by nature classification
idx_gl_accounts_status(tenant_id, status)B-treeActive account filtering; excludes INACTIVE/BLOCKED in UI and posting validation
idx_gl_accounts_currency(tenant_id, currency)B-treeCurrency-specific account filtering (migration 003)

4.2 gl_fiscal_periods -- Period Management

Purpose: Defines the calendar of fiscal periods for each tenant. Periods control which date ranges are open for posting and lock balances once closed. Supports up to 14 periods per fiscal year to accommodate Brazilian fiscal year conventions with adjustment periods.

Key characteristics:

  • Period numbers 13 and 14 are reserved for adjustment periods (is_adjustment = TRUE)
  • The state lifecycle is strictly unidirectional: FUTURE -> OPEN -> CLOSED -> LOCKED
  • closed_at/closed_by and locked_at/locked_by provide regulatory-grade audit timestamps for period state transitions
  • Journal entries cannot be posted to CLOSED or LOCKED periods (enforced by trigger trg_prevent_closed_period_entry)

Column Specification:

ColumnTypeNullableDefaultDescription
idUUIDNOT NULLuuid_generate_v4()Primary key
tenant_idUUIDNOT NULL--Tenant isolation key
fiscal_yearINTEGERNOT NULL--Four-digit fiscal year (e.g., 2025)
period_numberINTEGERNOT NULL--Period sequence within fiscal year; 1-12 standard, 13-14 adjustment
period_nameTEXTNOT NULL--Human-readable period label (e.g., "Janeiro 2025", "Ajuste Q4 2025")
start_dateDATENOT NULL--First calendar date of the period (inclusive)
end_dateDATENOT NULL--Last calendar date of the period (inclusive)
stategl_period_stateNOT NULL'FUTURE'Lifecycle state: FUTURE, OPEN, CLOSED, LOCKED
is_adjustmentBOOLEANNOT NULLFALSETRUE for special adjustment periods (periods 13-14)
closed_atTIMESTAMPTZNULL--Timestamp when period was moved to CLOSED state
closed_byUUIDNULL--User UUID who performed the period close operation
locked_atTIMESTAMPTZNULL--Timestamp when period was moved to LOCKED state
locked_byUUIDNULL--User UUID who performed the period lock operation
metadataJSONBNULL'{}'Extensible attributes
created_atTIMESTAMPTZNOT NULLNOW()Record creation timestamp
created_byUUIDNOT NULL--User UUID who created the record
updated_atTIMESTAMPTZNOT NULLNOW()Last modification timestamp
updated_byUUIDNOT NULL--User UUID who last modified the record

Constraints:

Constraint NameTypeDefinitionDescription
gl_fiscal_periods_pkeyPRIMARY KEY(id)Record identity
uq_period_tenant_year_numUNIQUE(tenant_id, fiscal_year, period_number)One period per number per year per tenant
ck_period_datesCHECKend_date >= start_dateEnd date must not precede start date
ck_period_numberCHECKperiod_number >= 1 AND period_number <= 14Period number within valid range; 1-12 standard, 13-14 adjustment

Indexes:

Index NameColumnsTypeDescription
idx_gl_periods_tenant(tenant_id)B-treeTenant-scoped period lookup
idx_gl_periods_state(tenant_id, state)B-treeFilters periods by lifecycle state; used when validating posting eligibility
idx_gl_periods_dates(tenant_id, start_date, end_date)B-treeDate-range queries to find the period containing a given entry date

4.3 gl_journal_entries -- Journal Entry Headers

Purpose: The header record for each accounting transaction. A journal entry groups one or more detail lines (gl_journal_lines) that must collectively balance debits to credits. The header captures the transaction's identity, date, status, module origin, and reversal linkage.

Key characteristics:

  • reference_number is a tenant-unique identifier (assigned by the creating module or user)
  • total_amount is a denormalized cache of the sum of absolute debit values; maintained by trigger
  • Reversal chains are modeled via bidirectional FK pairs: reverses_id (this entry reverses that one) and reversed_by_id (that entry reverses this one)
  • POSTED entries are immutable; modification is prevented by trigger trg_prevent_posted_modification
  • description is limited to 500 characters by constraint; this length is sufficient for all regulatory requirements including SPED

Column Specification:

ColumnTypeNullableDefaultDescription
idUUIDNOT NULLuuid_generate_v4()Primary key
tenant_idUUIDNOT NULL--Tenant isolation key
reference_numberTEXTNOT NULL--Unique transaction reference within tenant (e.g., JE-2025-001234)
entry_dateDATENOT NULL--Accounting date of the transaction; must fall within an OPEN period
descriptionTEXTNOT NULL--Narrative description; maximum 500 characters
statusgl_entry_statusNOT NULL'DRAFT'Lifecycle state: DRAFT, POSTED, REVERSED
module_sourcegl_module_sourceNOT NULL--Originating module: BUDGET, FORECAST, MANUAL, CONSOLIDATION, ADJUSTMENT, SYSTEM
period_idUUIDNOT NULL--FK to gl_fiscal_periods(id); period must be OPEN at time of posting
posted_atTIMESTAMPTZNULL--Timestamp when entry status changed to POSTED
posted_byUUIDNULL--User UUID who posted the entry
reversed_by_idUUIDNULL--FK to gl_journal_entries(id); the entry that reverses this one (populated on this entry when reversed)
reverses_idUUIDNULL--FK to gl_journal_entries(id); the original entry this entry reverses (populated on the reversal entry)
reversed_atTIMESTAMPTZNULL--Timestamp when the reversal was posted
total_amountNUMERIC(20,2)NOT NULL0Denormalized sum of debit amounts across all lines; maintained by trigger
metadataJSONBNULL'{}'Extensible attributes
created_atTIMESTAMPTZNOT NULLNOW()Record creation timestamp
created_byUUIDNOT NULL--User UUID who created the record
updated_atTIMESTAMPTZNOT NULLNOW()Last modification timestamp
updated_byUUIDNOT NULL--User UUID who last modified the record

Constraints:

Constraint NameTypeDefinitionDescription
gl_journal_entries_pkeyPRIMARY KEY(id)Record identity
gl_je_period_id_fkeyFOREIGN KEYperiod_id REFERENCES gl_fiscal_periods(id)Entry must reference a valid period
gl_je_reversed_by_id_fkeyFOREIGN KEYreversed_by_id REFERENCES gl_journal_entries(id)Self-referential; reversal chain back-pointer
gl_je_reverses_id_fkeyFOREIGN KEYreverses_id REFERENCES gl_journal_entries(id)Self-referential; reversal chain forward-pointer
uq_reference_tenantUNIQUE(tenant_id, reference_number)Reference numbers are unique per tenant
ck_description_lengthCHECKchar_length(description) <= 500Description capped at 500 characters

Indexes:

Index NameColumnsTypePredicateDescription
idx_gl_entries_tenant(tenant_id)B-tree--Primary tenant filter
idx_gl_entries_status(tenant_id, status)B-tree--Status-based filtering for DRAFT/POSTED/REVERSED queries
idx_gl_entries_period(tenant_id, period_id)B-tree--Period-scoped entry retrieval for balance calculation and reporting
idx_gl_entries_date(tenant_id, entry_date)B-tree--Date-range queries for transaction history
idx_gl_entries_source(tenant_id, module_source)B-tree--Module-segregated reporting (e.g., all BUDGET entries)
idx_gl_entries_reversal(reverses_id)B-treeWHERE reverses_id IS NOT NULLPartial index; efficient lookup of reversal entries without scanning non-reversal rows

4.4 gl_journal_lines -- Journal Entry Detail Lines

Purpose: The individual debit or credit postings that compose a journal entry. Each line posts to exactly one account, carries one side of the double-entry (debit XOR credit), and includes multi-currency fields for the original transaction currency alongside the functional-currency amount.

Key characteristics:

  • The ck_one_side_only constraint enforces that each line carries either a debit or a credit, never both and never neither
  • Multi-currency lines record original_currency, original_amount, and exchange_rate; functional-currency amounts in debit_amount/credit_amount are derived via original_amount * exchange_rate
  • Five generic dimension_N columns (plus cost_center_id and project_id) provide a flexible dimensional accounting structure without requiring schema changes as new dimensions are defined
  • Lines are immutable once the parent entry is POSTED
  • line_number is sequenced within a journal entry and must be unique per entry

Column Specification:

ColumnTypeNullableDefaultDescription
idUUIDNOT NULLuuid_generate_v4()Primary key
tenant_idUUIDNOT NULL--Tenant isolation key; must match parent journal entry
journal_entry_idUUIDNOT NULL--FK to gl_journal_entries(id)
account_idUUIDNOT NULL--FK to gl_accounts(id); must be an ACTIVE, non-header account
line_numberINTEGERNOT NULL--Sequential line number within the entry; 1-based
debit_amountNUMERIC(20,2)NOT NULL0Functional-currency debit amount; 0 when line is a credit
credit_amountNUMERIC(20,2)NOT NULL0Functional-currency credit amount; 0 when line is a debit
original_currencyCHAR(3)NOT NULL'BRL'ISO 4217 currency code of the original transaction
original_amountNUMERIC(20,2)NOT NULL0Amount in the original transaction currency
exchange_rateNUMERIC(18,8)NOT NULL1.0Exchange rate applied: original_currency to functional currency
cost_center_idUUIDNULL--Optional cost center dimension reference
project_idUUIDNULL--Optional project dimension reference
dimension_1TEXTNULL--Generic dimension slot 1 (e.g., product line)
dimension_2TEXTNULL--Generic dimension slot 2 (e.g., region)
dimension_3TEXTNULL--Generic dimension slot 3 (e.g., channel)
dimension_4TEXTNULL--Generic dimension slot 4 (reserved for future use)
dimension_5TEXTNULL--Generic dimension slot 5 (reserved for future use)
descriptionTEXTNULL--Line-level narrative; supplements the entry-level description
metadataJSONBNULL'{}'Extensible attributes
created_atTIMESTAMPTZNOT NULLNOW()Record creation timestamp
created_byUUIDNOT NULL--User UUID who created the record

Note: gl_journal_lines does not carry updated_at/updated_by columns because lines are immutable once the parent entry is posted. Pre-posting edits operate at the entry level; line corrections require entry reversal and re-entry after posting.

Constraints:

Constraint NameTypeDefinitionDescription
gl_journal_lines_pkeyPRIMARY KEY(id)Record identity
gl_jl_journal_entry_id_fkeyFOREIGN KEYjournal_entry_id REFERENCES gl_journal_entries(id)Line must belong to a valid entry
gl_jl_account_id_fkeyFOREIGN KEYaccount_id REFERENCES gl_accounts(id)Line must reference a valid account
ck_one_side_onlyCHECK(debit_amount > 0 AND credit_amount = 0) OR (debit_amount = 0 AND credit_amount > 0)Each line carries exactly one side; never both, never zero-zero
ck_positive_amountsCHECKdebit_amount >= 0 AND credit_amount >= 0Amounts cannot be negative
ck_exchange_rate_positiveCHECKexchange_rate > 0Exchange rate must be strictly positive
uq_journal_line_numberUNIQUE(journal_entry_id, line_number)Line numbers are unique within an entry

Indexes:

Index NameColumnsTypeDescription
idx_gl_lines_entry(journal_entry_id)B-treeRetrieves all lines for a given entry; primary access pattern
idx_gl_lines_account(tenant_id, account_id)B-treeAccount-centric queries for ledger and balance calculation
idx_gl_lines_tenant(tenant_id)B-treeFull tenant-scoped line scans

4.5 gl_account_balances -- Materialized Balance Cache

Purpose: Stores pre-computed account balances per period per currency. This table is a performance cache -- balances are derived from gl_journal_lines and maintained by the trg_update_balances trigger. It eliminates the need to aggregate all journal lines for every balance inquiry.

Key characteristics:

  • Balances are maintained incrementally by database trigger on every line insert/update affecting a posted entry
  • closing_balance = opening_balance + period_debits - period_credits (for DEBIT-normal accounts) or the inverse
  • The unique constraint uq_balance_account_period ensures exactly one row per account-period-currency combination
  • last_updated_at records the most recent trigger-driven update, distinct from created_at
  • If a balance row does not exist when a trigger fires, it is inserted; otherwise it is updated (upsert semantics)

Column Specification:

ColumnTypeNullableDefaultDescription
idUUIDNOT NULLuuid_generate_v4()Primary key
tenant_idUUIDNOT NULL--Tenant isolation key
account_idUUIDNOT NULL--FK to gl_accounts(id)
period_idUUIDNOT NULL--FK to gl_fiscal_periods(id)
currencyCHAR(3)NOT NULL'BRL'ISO 4217 currency code for this balance row
opening_balanceNUMERIC(20,2)NOT NULL0Balance carried forward from the prior period
period_debitsNUMERIC(20,2)NOT NULL0Total debit activity posted in this period
period_creditsNUMERIC(20,2)NOT NULL0Total credit activity posted in this period
closing_balanceNUMERIC(20,2)NOT NULL0Computed closing balance; maintained by trigger
last_updated_atTIMESTAMPTZNOT NULLNOW()Timestamp of last trigger-driven update
metadataJSONBNULL'{}'Extensible attributes
created_atTIMESTAMPTZNOT NULLNOW()Record creation timestamp
created_byUUIDNOT NULL--User UUID or system identifier that created the row

Constraints:

Constraint NameTypeDefinitionDescription
gl_account_balances_pkeyPRIMARY KEY(id)Record identity
gl_ab_account_id_fkeyFOREIGN KEYaccount_id REFERENCES gl_accounts(id)Must reference a valid account
gl_ab_period_id_fkeyFOREIGN KEYperiod_id REFERENCES gl_fiscal_periods(id)Must reference a valid period
uq_balance_account_periodUNIQUE(tenant_id, account_id, period_id, currency)One balance row per account-period-currency combination

Indexes:

Index NameColumnsTypeDescription
idx_gl_balances_tenant(tenant_id)B-treeTenant-scoped balance retrieval
idx_gl_balances_account(tenant_id, account_id)B-treeAccount ledger history across periods
idx_gl_balances_period(tenant_id, period_id)B-treePeriod-scoped trial balance generation

4.6 gl_exchange_rates -- Currency Exchange Rates

Purpose: Maintains the exchange rate registry used to convert foreign currency transactions into the functional currency (BRL). Supports multiple rate types per currency pair per date to accommodate different accounting translation requirements.

Key characteristics:

  • The unique constraint prevents duplicate rates for the same currency pair, rate type, and effective date
  • from_currency != to_currency is enforced by constraint; self-rates are not stored
  • Rates are stored with 8 decimal places (NUMERIC(18,8)) to preserve precision for currencies with large differentials
  • Rate lookup follows the effective date model: the rate with the most recent effective_date on or before the transaction date is used

Column Specification:

ColumnTypeNullableDefaultDescription
idUUIDNOT NULLuuid_generate_v4()Primary key
tenant_idUUIDNOT NULL--Tenant isolation key
from_currencyCHAR(3)NOT NULL--Source ISO 4217 currency code (e.g., USD)
to_currencyCHAR(3)NOT NULL--Target ISO 4217 currency code (e.g., BRL)
rate_typegl_rate_typeNOT NULL--Rate methodology: SPOT, AVERAGE, CLOSING
rateNUMERIC(18,8)NOT NULL--Exchange rate: 1 unit of from_currency = rate units of to_currency
effective_dateDATENOT NULL--Date from which this rate is valid
sourcegl_rate_sourceNOT NULL'MANUAL'Data provenance: MANUAL, BCB_API, BLOOMBERG, SYSTEM
metadataJSONBNULL'{}'Extensible attributes; stores API response metadata when source is BCB_API or BLOOMBERG
created_atTIMESTAMPTZNOT NULLNOW()Record creation timestamp
created_byUUIDNOT NULL--User UUID or system identifier

Constraints:

Constraint NameTypeDefinitionDescription
gl_exchange_rates_pkeyPRIMARY KEY(id)Record identity
uq_exchange_rateUNIQUE(tenant_id, from_currency, to_currency, rate_type, effective_date)One rate per pair-type-date combination
ck_rate_positiveCHECKrate > 0Exchange rates must be strictly positive
ck_different_currenciesCHECKfrom_currency != to_currencyPrevents self-rate entries

Indexes:

Index NameColumnsTypeDescription
idx_gl_rates_tenant(tenant_id)B-treeTenant-scoped rate retrieval
idx_gl_rates_pair(tenant_id, from_currency, to_currency, effective_date)B-treeOrdered pair lookup; supports "most recent rate on or before date" queries

4.7 gl_audit_log -- Immutable Audit Trail

Purpose: Records every state-changing operation on GL entities as an immutable, tamper-evident log. This table is the primary instrument for regulatory compliance (CVM, SPED, SOX-equivalent) and forensic audit. It is range-partitioned by created_at for query performance and operational manageability.

Key characteristics:

  • The composite primary key (id, created_at) is required by PostgreSQL's partitioning rules; both columns must be present in the primary key when partitioning by a non-key column
  • No UPDATE or DELETE is permitted on this table; enforced by trigger trg_prevent_audit_mutation
  • Partitioned into half-year segments from 2025 through 2027; new partitions must be created in advance before each half-year boundary
  • old_values and new_values store the full JSON snapshot of the record before and after the change
  • sped_export_id is populated when an audit entry is referenced by a SPED export, enabling export reconciliation

Column Specification:

ColumnTypeNullableDefaultDescription
idUUIDNOT NULLuuid_generate_v4()Record identifier; part of composite PK
tenant_idUUIDNOT NULL--Tenant isolation key
table_nameTEXTNOT NULL--Name of the source table that was modified (e.g., gl_journal_entries)
record_idUUIDNOT NULL--Primary key of the affected record in the source table
actiongl_audit_actionNOT NULL--Type of change: INSERT, UPDATE, STATUS_CHANGE
old_valuesJSONBNULL--Full JSON snapshot of the record before the change; NULL for INSERT actions
new_valuesJSONBNULL--Full JSON snapshot of the record after the change; NULL for hypothetical future DELETE actions
user_idUUIDNULL--UUID of the authenticated user who triggered the change
session_idUUIDNULL--Application session identifier for session-level audit correlation
ip_addressINETNULL--Client IP address at the time of the operation
module_sourceTEXTNULL--Originating module (TEXT here, not typed enum, to decouple audit log from module changes)
sped_export_idUUIDNULL--FK to gl_sped_exports(id); populated during SPED export generation
created_atTIMESTAMPTZNOT NULLNOW()Event timestamp; partition key

Primary Key: Composite (id, created_at) -- required for partitioned table compatibility in PostgreSQL.

Partitioning Strategy:

Partition NameRange StartRange EndCovers
gl_audit_log_2025_h12025-01-012025-07-01Jan-Jun 2025
gl_audit_log_2025_h22025-07-012026-01-01Jul-Dec 2025
gl_audit_log_2026_h12026-01-012026-07-01Jan-Jun 2026
gl_audit_log_2026_h22026-07-012027-01-01Jul-Dec 2026
gl_audit_log_2027_h12027-01-012027-07-01Jan-Jun 2027
gl_audit_log_2027_h22027-07-012028-01-01Jul-Dec 2027

Operational note: Partitions for 2028 and beyond must be created before January 1, 2028. A maintenance procedure or scheduled job should be established to pre-create partitions six months in advance of their activation date.

Indexes:

Index NameColumnsTypePredicateDescription
idx_gl_audit_tenant(tenant_id, created_at)B-tree--Time-ordered audit history per tenant; primary access pattern for audit reports
idx_gl_audit_record(table_name, record_id, created_at)B-tree--Full history for a specific record; used by entity detail audit views
idx_gl_audit_sped(sped_export_id)B-treeWHERE sped_export_id IS NOT NULLPartial index; efficient lookup of audit entries associated with a SPED export

Note on index propagation: Indexes created on the parent partitioned table are automatically propagated to all existing and future partitions by PostgreSQL 16.


4.8 gl_sped_exports -- SPED Export Tracking

Purpose: Tracks the lifecycle and outcome of each SPED (Sistema Publico de Escrituracao Digital) export job. Records the export type, fiscal scope, processing status, file integrity hash, and any validation errors returned by pre-submission validation.

Key characteristics:

  • Supports three export types: ECD (Escrituracao Contabil Digital), ECF (Escrituracao Contabil Fiscal), EFD (Escrituracao Fiscal Digital)
  • file_hash stores the SHA-256 hash of the generated SPED file for integrity verification and submission reconciliation
  • validation_result stores the structured output of pre-submission validation rules in JSONB format
  • error_details captures failure information when status is FAILED
  • The sped_export_id on gl_audit_log rows links audit records to the export that triggered them

Column Specification:

ColumnTypeNullableDefaultDescription
idUUIDNOT NULLuuid_generate_v4()Primary key
tenant_idUUIDNOT NULL--Tenant isolation key
export_typeTEXTNOT NULL--SPED module: ECD, ECF, or EFD
fiscal_yearINTEGERNOT NULL--Four-digit fiscal year covered by this export
period_startDATENOT NULL--First date of the reporting period
period_endDATENOT NULL--Last date of the reporting period
statusTEXTNOT NULL'PENDING'Lifecycle state: PENDING, GENERATING, COMPLETED, FAILED
file_hashTEXTNULL--SHA-256 hash of the generated file; NULL until COMPLETED
file_size_bytesBIGINTNULL--Size of the generated file in bytes; NULL until COMPLETED
record_countINTEGERNULL--Number of SPED records written to the file; NULL until COMPLETED
validation_resultJSONBNULL--Structured pre-submission validation output; fields: passed (boolean), warnings (array), errors (array)
error_detailsJSONBNULL--Failure details when status = 'FAILED'; fields: error_code, error_message, stack_trace
metadataJSONBNULL'{}'Extensible attributes
created_atTIMESTAMPTZNOT NULLNOW()Record creation timestamp
created_byUUIDNOT NULL--User UUID who initiated the export
completed_atTIMESTAMPTZNULL--Timestamp when export reached COMPLETED or FAILED state

Constraints:

Constraint NameTypeDefinitionDescription
gl_sped_exports_pkeyPRIMARY KEY(id)Record identity

Note: export_type and status are intentionally TEXT columns rather than enum types. The SPED program evolves its module taxonomy over time; TEXT avoids the need for DDL changes when new export types or states are introduced, while validation_result JSONB accommodates evolving validation rule sets without schema changes.

Indexes:

Index NameColumnsTypeDescription
idx_gl_sped_tenant(tenant_id)B-treeTenant-scoped export history

5. Migration History

Migrations are located at:

GL-PROTOTYPE/src/schema/migrations/

They are applied in sequential order. Each migration is idempotent where technically feasible (IF NOT EXISTS guards on index creation; IF NOT EXISTS on column additions). The runner script (src/schema/migrate.ts) tracks applied versions and will not re-apply a completed migration.


5.1 001_core_schema.sql

Applied: Initial deployment Classification: Foundation migration

Summary: Creates the entire GL Engine schema from scratch. This is the baseline migration that all subsequent migrations build upon.

Objects Created:

Extensions:

  • uuid-ossp
  • pgcrypto

Enum Types (10):

  • gl_account_type
  • gl_normal_balance
  • gl_account_status
  • gl_period_state
  • gl_entry_status
  • gl_module_source
  • gl_rate_type
  • gl_rate_source
  • gl_audit_action
  • gl_sped_natura

Tables (8):

  • gl_accounts
  • gl_fiscal_periods
  • gl_journal_entries
  • gl_journal_lines
  • gl_account_balances
  • gl_exchange_rates
  • gl_audit_log (partitioned parent)
  • gl_sped_exports

Partitions (6):

  • gl_audit_log_2025_h1 through gl_audit_log_2027_h2

Indexes: All indexes documented in Section 4, excluding idx_gl_accounts_currency (added in migration 003).

Rollback note: Rolling back migration 001 drops all GL Engine objects. This is a destructive operation that must only be performed in development environments.


5.2 002_account_name_translations.sql

Applied: After 001_core_schema.sql Classification: Additive -- no breaking changes

Summary: Adds tri-lingual account name support to gl_accounts. The original account_name column is retained as the canonical Portuguese name. English and Spanish translations are stored in new nullable columns.

Objects Modified:

ALTER TABLE gl_accounts
ADD COLUMN IF NOT EXISTS account_name_en TEXT,
ADD COLUMN IF NOT EXISTS account_name_es TEXT;

COMMENT ON COLUMN gl_accounts.account_name_en IS 'English translation of account_name';
COMMENT ON COLUMN gl_accounts.account_name_es IS 'Spanish translation of account_name';

Impact:

  • Both columns are nullable; existing rows receive NULL values, which is acceptable since translations are populated asynchronously by the AI translation service
  • No index was added; translation-based filtering is not an expected query pattern
  • No constraints were added; translation accuracy is enforced at the application layer

Rollback:

ALTER TABLE gl_accounts
DROP COLUMN IF EXISTS account_name_en,
DROP COLUMN IF EXISTS account_name_es;

5.3 003_account_currency.sql

Applied: After 002_account_name_translations.sql Classification: Additive -- no breaking changes

Summary: Adds a per-account functional currency column to gl_accounts. This corrects a design omission discovered during frontend integration: the UI expected a currency attribute on account records but the column was absent from the initial schema.

Root cause: The initial schema assumed currency would always be determined at the journal line level (gl_journal_lines.original_currency). The frontend integration revealed that account-level currency is needed for Chart of Accounts display, validation UI, and multi-currency account grouping.

Objects Modified:

ALTER TABLE gl_accounts
ADD COLUMN IF NOT EXISTS currency CHAR(3) NOT NULL DEFAULT 'BRL';

CREATE INDEX IF NOT EXISTS idx_gl_accounts_currency
ON gl_accounts(tenant_id, currency);

Impact:

  • NOT NULL DEFAULT 'BRL' ensures existing accounts receive a valid default without a data migration step
  • The default reflects the primary operating currency for Brazilian fiscal environments
  • Accounts transacting in foreign currencies should have their currency field updated after migration
  • The new index idx_gl_accounts_currency supports currency-segmented account filtering

Rollback:

DROP INDEX IF EXISTS idx_gl_accounts_currency;
ALTER TABLE gl_accounts DROP COLUMN IF EXISTS currency;

6. Entity Relationship Summary

The following diagram describes the primary relationships between GL Engine tables:

gl_accounts (self-referential via parent_id)
|
+--< gl_journal_lines.account_id
|
+--< gl_account_balances.account_id

gl_fiscal_periods
|
+--< gl_journal_entries.period_id
|
+--< gl_account_balances.period_id

gl_journal_entries (self-referential via reverses_id / reversed_by_id)
|
+--< gl_journal_lines.journal_entry_id

gl_sped_exports
|
+--< gl_audit_log.sped_export_id (partial; nullable)

Key relationship properties:

RelationshipCardinalityCascade Behavior
gl_accounts -> gl_accounts (parent)Many-to-one (tree)No cascade; parent deletion requires child reassignment
gl_fiscal_periods -> gl_journal_entriesOne-to-manyNo cascade; period deletion requires entry removal first
gl_journal_entries -> gl_journal_linesOne-to-manyNo cascade; entry deletion requires line removal first
gl_accounts -> gl_journal_linesOne-to-manyNo cascade
gl_accounts -> gl_account_balancesOne-to-many (by period)No cascade
gl_fiscal_periods -> gl_account_balancesOne-to-many (by account)No cascade
gl_journal_entries -> gl_journal_entries (reversal)One-to-one (optional)No cascade

Note: No ON DELETE CASCADE rules are defined. The GL Engine enforces referential integrity conservatively; deletion of parent records requires explicit cleanup or soft-deletion via status transitions. Hard deletes on financial records are not a supported operation.


7. Index Inventory

Complete listing of all indexes in the GL Engine schema after all three migrations are applied.

Index NameTableColumnsTypePredicateAdded By
idx_gl_accounts_tenantgl_accounts(tenant_id)B-tree--001
idx_gl_accounts_parentgl_accounts(tenant_id, parent_id)B-tree--001
idx_gl_accounts_typegl_accounts(tenant_id, account_type)B-tree--001
idx_gl_accounts_spedgl_accounts(tenant_id, sped_natura)B-tree--001
idx_gl_accounts_statusgl_accounts(tenant_id, status)B-tree--001
idx_gl_accounts_currencygl_accounts(tenant_id, currency)B-tree--003
idx_gl_periods_tenantgl_fiscal_periods(tenant_id)B-tree--001
idx_gl_periods_stategl_fiscal_periods(tenant_id, state)B-tree--001
idx_gl_periods_datesgl_fiscal_periods(tenant_id, start_date, end_date)B-tree--001
idx_gl_entries_tenantgl_journal_entries(tenant_id)B-tree--001
idx_gl_entries_statusgl_journal_entries(tenant_id, status)B-tree--001
idx_gl_entries_periodgl_journal_entries(tenant_id, period_id)B-tree--001
idx_gl_entries_dategl_journal_entries(tenant_id, entry_date)B-tree--001
idx_gl_entries_sourcegl_journal_entries(tenant_id, module_source)B-tree--001
idx_gl_entries_reversalgl_journal_entries(reverses_id)B-treeWHERE reverses_id IS NOT NULL001
idx_gl_lines_entrygl_journal_lines(journal_entry_id)B-tree--001
idx_gl_lines_accountgl_journal_lines(tenant_id, account_id)B-tree--001
idx_gl_lines_tenantgl_journal_lines(tenant_id)B-tree--001
idx_gl_balances_tenantgl_account_balances(tenant_id)B-tree--001
idx_gl_balances_accountgl_account_balances(tenant_id, account_id)B-tree--001
idx_gl_balances_periodgl_account_balances(tenant_id, period_id)B-tree--001
idx_gl_rates_tenantgl_exchange_rates(tenant_id)B-tree--001
idx_gl_rates_pairgl_exchange_rates(tenant_id, from_currency, to_currency, effective_date)B-tree--001
idx_gl_audit_tenantgl_audit_log(tenant_id, created_at)B-tree--001
idx_gl_audit_recordgl_audit_log(table_name, record_id, created_at)B-tree--001
idx_gl_audit_spedgl_audit_log(sped_export_id)B-treeWHERE sped_export_id IS NOT NULL001
idx_gl_sped_tenantgl_sped_exports(tenant_id)B-tree--001

Total indexes: 27 (26 from migration 001, 1 from migration 003)


8. Design Notes and Conventions

Naming Conventions

All schema objects follow a consistent prefix and naming convention:

Object TypePrefixExample
Tablesgl_gl_journal_entries
Enum typesgl_gl_account_type
Primary keys{table}_pkeygl_accounts_pkey
Foreign keys{table}_{column}_fkeygl_jl_account_id_fkey
Unique constraintsuq_{descriptive_name}uq_account_number_tenant
Check constraintsck_{descriptive_name}ck_one_side_only
Indexesidx_{table}_{columns}idx_gl_lines_account
Partitions{parent}_{year}_{h1|h2}gl_audit_log_2026_h1

Primary Key Strategy

All tables use UUID primary keys generated by uuid_generate_v4(). This choice:

  • Eliminates sequential ID enumeration attacks
  • Supports distributed ID generation without coordination
  • Enables safe data migration and merge between tenant environments
  • Is consistent with the application layer's UUID-first identity model

Monetary Precision

All monetary columns use NUMERIC(20,2):

  • 20 total digits accommodates amounts up to 999,999,999,999,999,999.99 (quintillions) -- sufficient for any foreseeable financial context
  • 2 decimal places is standard for BRL and most G20 currencies
  • Exchange rates use NUMERIC(18,8) to preserve 8 decimal places of precision for currency conversions

Tenant Isolation Pattern

Every table carries tenant_id UUID NOT NULL. This column is:

  • Never given a default value (explicit assignment required)
  • Included as the leading column in all non-trivial indexes to ensure index use under Row-Level Security policies
  • The basis for all RLS policies (documented in CFS-DB-003)
  • Verified at the application layer before any database operation

Audit Column Pattern

All mutable tables include the following audit columns:

ColumnTypeDescription
created_atTIMESTAMPTZ NOT NULL DEFAULT NOW()Set at insert time; never updated
created_byUUID NOT NULLAuthenticated user UUID at creation time
updated_atTIMESTAMPTZ NOT NULL DEFAULT NOW()Updated by trigger on every modification
updated_byUUID NOT NULLSet to authenticated user UUID on every modification

The gl_journal_lines table intentionally omits updated_at/updated_by because lines are immutable after the parent entry is posted.

The gl_audit_log table omits all update audit columns because it is an append-only table.

JSONB Metadata Pattern

All tables include a metadata JSONB DEFAULT '{}' column. This column:

  • Provides a structured escape hatch for integration-specific attributes without requiring schema changes
  • Is indexed by applications using expression indexes where specific JSONB paths are queried frequently
  • Must not be used to store information that belongs in a typed column; it is an extension point, not a substitute for schema design

SPED Compliance Design

Brazilian SPED compliance requirements influenced several schema decisions:

  • gl_sped_natura enum encodes the six account nature classifications required by ECD
  • sped_cod_agl and sped_referencial_code on gl_accounts map to ECD register fields directly
  • gl_sped_exports provides a full audit trail of every export job, including file hash verification
  • gl_audit_log.sped_export_id creates a bidirectional link between audit entries and the exports that reference them
  • gl_fiscal_periods.is_adjustment supports Brazilian fiscal year conventions that include adjustment periods beyond the standard 12 months

Document CodeTitleRelationship
CFS-DB-001GL Engine Architecture OverviewParent document; system-level context for this schema
CFS-DB-003Security and Multi-Tenancy ModelDescribes RLS policies applied to the tables documented here
CFS-DB-004Business Logic Layer -- Triggers and Stored FunctionsDocuments triggers that enforce double-entry, balance maintenance, and audit trail on this schema
CFS-DB-005Migration RoadmapForward-looking schema evolution plan; next migrations pending

Source of Truth for Schema: The migration files at GL-PROTOTYPE/src/schema/migrations/ are the authoritative definition. This document reflects their state as of 2026-02-20 (migrations 001 through 003 applied).


Copyright 2026 AZ1.AI Inc. All rights reserved. INTERNAL CONFIDENTIAL -- Authorized Personnel Only.

Document Code: CFS-DB-002 | Version: 1.0.0 | Last Updated: 2026-02-20 Owner: Hal Casteel, CEO/CTO, AZ1.AI Inc.