Ledger Integrity Verification

Automated general ledger health verification with five core integrity checks, immutable scan snapshots, severity-based finding management, and integration with the integrity gate.

7 min read Accounting Controls As of Feb 9, 2026

CommunityPay runs automated integrity checks against the general ledger to verify that the accounting data is internally consistent. This page describes what those checks examine, what they produce, and how findings connect to payment controls.

Purpose

A general ledger can develop inconsistencies through several mechanisms: application bugs, interrupted transactions, manual corrections applied incorrectly, migration errors, or race conditions in concurrent posting. Most of these are rare. Some are inevitable over a long enough timeline.

The question is not whether inconsistencies will occur, but whether they will be detected when they do. CommunityPay's integrity scan converts "we believe the ledger is correct" into "we can demonstrate the ledger is correct as of this scan."

The Five Checks

1. Balance Verification

What it checks: Every journal entry in the ledger has total debits equal to total credits.

This is the most fundamental accounting invariant. A journal entry where debits do not equal credits indicates a broken posting — either a line was added or removed after the entry was created, or the posting logic produced an unbalanced entry.

On failure: Each unbalanced entry is recorded as a finding with the debit total, credit total, and difference amount.

2. Orphan Line Detection

What it checks: Every journal entry line has a valid parent journal entry.

An orphaned line is a line item that exists in the database but is not associated with a journal entry. This can occur if a journal entry is deleted (which should not happen — entries are immutable) or if a line is created through a code path that bypasses the JournalEngine.

On failure: Each orphaned line is recorded as a finding with the line ID and account reference.

3. Enforcement Coverage

What it checks: Every journal entry created after the enforcement cutover date (January 24, 2025) has an associated EnforcementDecision.

The enforcement dispatcher is the mandatory choke point for all financial decisions. If a journal entry exists without an EnforcementDecision, it means the entry was created through a code path that bypassed the dispatcher. This is either a bug or a policy violation.

Cutover scoping: Journal entries created before January 24, 2025 are excluded from this check. The enforcement dispatcher was activated on that date. Entries before that date were created under the prior system and are not expected to have enforcement decisions.

On failure: Each entry without an enforcement decision is recorded as a finding with the entry ID, posting date, and amount.

4. Fund Assignment

What it checks: Journal entry lines that touch fund-tracked accounts have a fund assignment.

Fund accounting requires that every transaction affecting a fund's accounts is assigned to the correct fund. A line without a fund assignment means the transaction's impact on fund balances is ambiguous.

On failure: Each unassigned line is recorded as a finding with the line ID, account, and amount.

5. Closed Period Compliance

What it checks: No journal entries have posting dates that fall within a closed accounting period.

When an accounting period is closed, no new entries should be posted to that period. An entry in a closed period indicates either a bug in the period-closing logic or a manual intervention that bypassed the ClosedPeriodGuard.

On failure: Each entry in a closed period is recorded as a finding with the entry ID, posting date, and the closed period reference.

IntegritySnapshot

Every scan produces an IntegritySnapshot — an immutable record of the scan results.

What It Contains

Field Description
Snapshot ID Unique identifier (UUID) for external reference
HOA Which HOA was scanned
Status Overall result: GREEN, YELLOW, or RED
Check results Pass/warn/fail status for each of the five checks
Finding counts CRITICAL, WARNING, and INFO findings
Ledger metrics Total journal entries and enforcement decisions at scan time
Content hash SHA-256 hash of the snapshot content
Scanned at Timestamp of the scan
Scanned by User who triggered the scan (null for automated scans)
Duration Execution time in milliseconds

Status Computation

The overall status is computed deterministically:

Condition Status
Any check returns FAIL RED
Any check returns WARN (and none FAIL) YELLOW
All checks return PASS GREEN

There is no weighting, no scoring, and no threshold configuration. A single failed check turns the entire scan RED. This is intentional — an accounting system with known integrity failures should not report a "mostly healthy" status.

Immutability

IntegritySnapshot inherits from both ImmutableModelMixin and ContentHashMixin. Once created, the snapshot cannot be modified or deleted. The content hash is computed before the first save and is included in the immutable field set.

This means a scan result from six months ago can be verified: recompute the content hash from the snapshot's fields and compare with the stored hash. If they match, the scan result has not been tampered with.

IntegrityFinding

Individual issues discovered during a scan are recorded as IntegrityFinding records.

Severity Levels

Severity Meaning Example
CRITICAL Ledger integrity is compromised; immediate action required Unbalanced journal entry, entry in closed period
WARNING Potential issue that should be investigated Missing fund assignment on a non-exempt account
INFO Informational observation, no action required Legacy entry without enforcement decision (pre-cutover)

Finding Lifecycle

Findings have a status: OPEN, ACKNOWLEDGED, or RESOLVED. New findings are created as OPEN. Staff can acknowledge a finding (indicating awareness without resolution) or resolve it (indicating the underlying issue has been corrected).

On subsequent scans, the service checks whether previously recorded findings still exist:

  • If a finding's underlying condition persists, the finding is updated with the latest scan reference
  • If the condition has been corrected, the finding is marked RESOLVED
  • New conditions produce new findings

Finding Cap

Each check is capped at 50 findings per scan. If a check discovers more than 50 issues, only the first 50 are recorded as individual findings. The check result still reflects the total count.

This cap exists to prevent a single systemic issue (e.g., a migration that broke fund assignments on thousands of entries) from overwhelming the findings table. The check result captures the full scope; the findings table captures enough detail for investigation.

Integration with the Integrity Gate

CRITICAL integrity findings connect to the payment system through the integrity gate, described in the Governance Controls trust center article.

The connection is straightforward:

  1. A ledger integrity scan discovers a CRITICAL finding
  2. The finding is recorded with status OPEN
  3. Before every payment operation, the system checks for OPEN or ACKNOWLEDGED CRITICAL findings
  4. If any exist and no active AuditOverride with scope INTEGRITY_GATE is present, the payment is blocked

This means a CRITICAL ledger issue discovered at 2 AM by an automated scan will block payment processing when the next payment is attempted — even if no human has seen the finding yet. The gate is automatic.

Breaking the Gate

The only way to process payments while CRITICAL findings exist is to create a time-limited AuditOverride:

  • Maximum duration: 24 hours (system-enforced)
  • Requires documented justification
  • Creates an immutable audit record
  • Automatically expires — the gate re-engages when the override lapses

Alternatively, the underlying issue can be resolved, which clears the finding and reopens the gate without an override.

Scheduling

The integrity scan can be triggered in two ways:

  1. Automated: Scheduled as a daily Celery task, typically running in the early morning hours
  2. Manual: Triggered through the operations interface by platform staff

Both paths produce identical IntegritySnapshot records. The only difference is the scanned_by field — automated scans record null; manual scans record the triggering user.

What This Proves

A series of IntegritySnapshots over time proves:

  • The ledger was verified on each scan date
  • The specific checks that were performed and their results
  • Whether findings were detected, acknowledged, and resolved
  • That each scan result is tamper-evident (content hash)

An auditor reviewing an HOA's books can request the IntegritySnapshot history and verify that the ledger has been continuously monitored, that issues were detected and resolved, and that the scan results themselves have not been modified after the fact.

This is the difference between "we run checks" and "we can prove we run checks, and we can prove the results haven't been altered."

How CommunityPay Enforces This
  • Five automated integrity checks: balance verification, orphan detection, enforcement coverage, fund assignment, closed period compliance
  • IntegritySnapshot is immutable (ImmutableModelMixin) with SHA-256 content hash via ContentHashMixin
  • Enforcement cutover date (January 24, 2025) scopes enforcement coverage checks to avoid false positives on legacy entries
  • Findings capped at 50 per check to prevent database overload while preserving detection fidelity
  • CRITICAL findings trigger the integrity gate, blocking all payments for the affected HOA until resolved or overridden
  • Overall status computed deterministically: any FAIL = RED, any WARN = YELLOW, all PASS = GREEN
Login