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:
- A ledger integrity scan discovers a CRITICAL finding
- The finding is recorded with status OPEN
- Before every payment operation, the system checks for OPEN or ACKNOWLEDGED CRITICAL findings
- 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:
- Automated: Scheduled as a daily Celery task, typically running in the early morning hours
- 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