Fund Accounting & Segregation Controls

How CommunityPay enforces operating and reserve fund boundaries at the ledger level. Guard-driven segregation, fund policies, transfer workflows, and the difference between tagging and enforcement.

9 min read Accounting Controls Published Oct 3, 2024

Fund accounting is the practice of segregating an organization's money into distinct pools — each with its own constraints, its own balance, and its own reporting. For HOAs, the most important boundary is between the operating fund and the reserve fund.

CommunityPay enforces fund segregation at the ledger level. This page describes how that enforcement works, what it prevents, and why "tagging" transactions with fund labels is not the same as enforcing fund boundaries.

The Problem with Tagging

A common way to track funds is to tag transactions with a fund label. The transaction is posted to the general ledger, and the fund label is metadata attached after the fact.

This approach has a structural weakness: the tag is advisory. Nothing prevents a user from posting a transaction that moves cash between funds without changing the tag. Nothing prevents a report from aggregating tagged and untagged transactions. Nothing prevents a migration or bulk import from omitting tags entirely.

The result is that fund balances in tag-based systems are only as reliable as the discipline of the people entering data. For an HOA with a volunteer board and a rotating property manager, that discipline is inconsistent.

CommunityPay takes a different approach. Fund assignment is not a tag — it is a constraint enforced by the same guard system that enforces double-entry balance, closed period restrictions, and subledger reconciliation.

FundSegregationGuard

The FundSegregationGuard is one of 18 guards the enforcement dispatcher runs. It runs on every journal entry that is not an explicit fund transfer.

What It Checks

For each journal entry, the guard examines which bank-reconciled cash accounts are touched by the entry's lines. It then determines which funds those accounts belong to.

The guard flags a violation when:

  • The entry touches cash accounts from two or more different funds in a single posting
  • The entry touches cash from one fund plus untagged accounts that could represent a different fund

What It Excludes

The guard recognizes three explicit transfer transaction types:

Transaction Type Direction
transfer_to_reserve Operating to Reserve
transfer_from_reserve Reserve to Operating
fund_equity_transfer Any inter-fund equity movement

When the transaction type is one of these, the guard passes without evaluation. These are the only code paths that are permitted to cross fund boundaries.

The guard also excludes accounts that are not bank-reconciled cash — clearing accounts, undeposited funds, suspense accounts, and accounts receivable holding accounts. These accounts exist to facilitate transaction processing and are not subject to fund segregation rules.

What Happens on Failure

If the guard detects cross-fund cash movement on a non-transfer transaction, it returns a failure with reason code cross_fund_cash_movement. The enforcement dispatcher records the failure in an EnforcementDecision, and the journal entry is blocked.

The failure message identifies the violation and suggests two remediation paths:

  1. Reclassify the transaction as a transfer type
  2. Create an AuditOverride with scope FUND_SEGREGATION (which requires documented justification and is itself an immutable audit record)

Override Path

The guard supports overrides through the AuditOverride system. An authorized user can create a time-limited override with scope FUND_SEGREGATION. The override must include:

  • Justification, of at least twenty characters, for why the override is necessary (writing an override takes the Change rules ability and a fresh authenticator code)
  • The one fund it applies to
  • An expiry, which for this scope may be at most fourteen days out

The override is itself an immutable record. Every journal entry that executes under an override links to the override in its EnforcementDecision. This means the audit trail shows not just that the entry was allowed, but why it was allowed and who authorized the exception.

FundConsistencyGuard

A second guard, fund_consistency, applies to every flow and cannot be overridden. Where the segregation guard fails an entry that touches cash in two funds, the consistency guard asks whether the one cash account an entry touches belongs to the fund the entry is posted in. Every fund-boundary cash account on an entry must belong to the entry's own fund. A payment out of operating cash tagged as a reserve entry fails it, because the reserve's statements would show reserve cash leaving while the operating account is the one that moved.

The guard also closes the transfer path. A posting of transfer_to_reserve or transfer_from_reserve must carry out one approved fund transfer of the association — the same amount and the same two cash accounts, approved by someone the rules authorize and posted by someone who may pay — or it does not post. fund_equity_transfer reclassifies equity between funds and moves no cash, so it has no disbursement for a board to authorize.

Each fund balances on its own: the ledger adds the interfund lines that keep every fund in balance, a reserve bill is recorded to reserve payables, and reserve interest credits the reserve's interest account. A reserve bill is paid online only after the board-approved transfer that moves its money to operating has posted.

Fund Model

Each HOA has one or more funds, each with a distinct type:

Fund Type Purpose
OPERATING Day-to-day operational expenses
RESERVE Long-term capital replacement
SPECIAL Special assessment collections
CAPITAL Capital improvement projects

Balance Computation

Fund balances are computed from the general ledger, not maintained as a separate field. The get_balance() method sums the balances of all accounts assigned to the fund, applying the accounting equation:

Fund Balance = Total Assets - Total Liabilities

A separate get_equity_balance() method sums only the equity accounts assigned to the fund. In a properly balanced fund, these two values are equal. The get_balance_summary() method returns both values and flags whether they agree — a built-in reconciliation check.

Funding Adequacy

For reserve funds, the model tracks:

Field Source
Funding target Set from the most recent professional reserve study
Percent funded Current balance divided by funding target
Funding status fully_funded (>=100%), adequate (70-99%), underfunded (30-69%), critically_underfunded (<30%)

These values are computed on demand from the ledger, not cached. The percent funded that appears in a report is the percent funded at the moment the report is generated.

Fund Policies

Fund policies are declarative constraints that govern how fund money can be spent, transferred, or received. Each policy has a type, a set of rules, and an enforcement level. Changing a fund policy asks for a fresh authenticator code.

Policy Types

Type What It Controls At payment
EXPENSE_CATEGORY Which expense accounts can be charged to this fund Enforced
APPROVAL_THRESHOLD Requires board vote or a specific approver seat at or above a dollar amount Enforced
PROJECT_RESTRICTION Limits spending to a specific project, with optional vendor and budget restrictions Recorded
TRANSFER_RESTRICTION Limits which funds can receive transfers, with maximum amounts and approval requirements Recorded
SOURCE_RESTRICTION Controls which revenue sources can contribute to this fund Recorded

The fund_policy guard enforces the first two on bill payments. It reads the expense accounts the bill charges, and the bill's live approval for the board vote or approver seat. The other three types are recorded and are not enforced by the guard.

Enforcement Levels

Level Behavior
BLOCK The payment is refused if it violates the policy
WARN The payment posts, and the finding is recorded in the guard's evidence on the enforcement decision
AUDIT The payment posts, and the finding is recorded in the guard's evidence on the enforcement decision

The guard cannot be overridden. The board's levers are the policy itself: its enforcement level, its end date, and its active flag. A policy that exists but cannot be evaluated — the bill is missing, its expense accounts cannot be resolved, a required role has no record — is a finding against that policy, judged at the policy's own level. Policies are selected as of the payment's effective date, so a policy dated after the payment does not govern it.

Policy State on Every Decision

The fund policies in force, the association's duty settings, and the active eligibility rules and exclusion triggers are hashed into the policy_snapshot_hash recorded on every enforcement decision, and the canonical description behind each hash is stored and never modified. The policy that governed a payment on January 15 can therefore be reproduced exactly, even if the policy was edited or ended afterward.

Fund Transfer Workflow

Moving money between funds is a controlled operation with a mandatory approval workflow.

Transfer Lifecycle

DRAFT → PENDING_APPROVAL → APPROVED → COMPLETED
                                ↘ REJECTED
         ↘ CANCELLED
  • DRAFT: Created but not submitted. Can be edited.
  • PENDING_APPROVAL: Submitted for board review. Cannot be edited.
  • APPROVED: Board has approved. Ready for execution.
  • COMPLETED: Transfer executed. Journal entry created via JournalEngine.
  • REJECTED: Board rejected. Cannot be resubmitted (new transfer required).
  • CANCELLED: Withdrawn before completion.

No backward transitions. A rejected transfer cannot be re-opened. A completed transfer cannot be reversed through the transfer workflow — it requires a separate reversal entry through the enforcement dispatcher, and a reversed transfer entry leaves its transfer marked REVERSED.

Journal Entry Creation

When a transfer is completed, it creates a journal entry through the JournalEngine — the same choke point used by all financial transactions. The entry:

  • Debits the destination fund's cash account
  • Credits the source fund's cash account
  • Passes through the enforcement dispatcher (which skips the FundSegregationGuard for transfer types, and whose FundConsistencyGuard requires the approved transfer)
  • Creates an immutable EnforcementDecision

The transfer record links to the resulting journal entry, creating a traceable chain from the board approval to the ledger posting.

Validation

Fund transfers enforce several constraints:

  • Amount must be greater than zero
  • Source and destination funds must be different
  • Source and destination accounts must belong to their respective funds
  • Justification is required before submission for approval

These constraints are enforced at the model level, not just in the UI. A transfer that violates them cannot be saved regardless of how the save is attempted.

CARI Integration

Fund accounting health signals, including the reserve ratio and the operating ratio, feed the CARI Financial Health sub-score, which carries a 30% weight, the heaviest single component in the composite score.

For published methodology and component weights, see CARI Methodology and Scoring Framework.


This de

How CommunityPay Enforces This
  • FundSegregationGuard blocks cross-fund cash movement on every non-transfer journal entry
  • FundConsistencyGuard refuses an entry whose cash line sits in another fund, and cannot be overridden
  • Transfer transactions require explicit type declaration (transfer_to_reserve, transfer_from_reserve, fund_equity_transfer), and a cash transfer posts only against an approved FundTransfer
  • Reserve money leaves the reserve only by a board-approved transfer, and a reserve bill is paid online only after the transfer that funds it has posted
  • The fund policy guard enforces two policy types at payment — which expense accounts may draw from the fund, and the board vote or approver seat required at or above an amount — and the policies in force are hashed into every enforcement decision
  • Fund transfers follow a mandatory approval workflow: DRAFT to PENDING_APPROVAL to APPROVED to COMPLETED
  • Fund balance computed from the double-entry ledger (assets minus liabilities), not from a separate tracking field
Login