CommunityPay's governance layer sits below the payment layer. All production financial flows pass through enforcement controls before execution. This is not optional behavior that users can disable — it is architectural.
EnforcementDecision: The Choke Point
All production financial flows in CommunityPay — journal entries, fund transfers, bill payments, invoice creation, payment receipts, and fiscal year closures — must produce an EnforcementDecision before they can proceed.
An EnforcementDecision records:
- The decision: ALLOW, BLOCK, OVERRIDE, or ERROR
- Which guards evaluated: What rules were expected to run, which actually ran, and what each concluded
- The timing: Elapsed milliseconds per guard, total evaluation time
- The context: Which HOA, which user, which flow, what amount
This creates a complete, immutable record of every financial decision and the reasoning behind it.
Two-Event Pattern
Each enforced transaction generates two events:
- PRE_PERSIST (decision_seq=0): The enforcement evaluation happens before the database commit. If the decision is BLOCK, the transaction never touches the ledger.
- POST_PERSIST (decision_seq=1): After a successful commit, a confirmation event is recorded with the resulting journal entry foreign key.
If a commit fails after an ALLOW decision, a PERSIST_ERROR (decision_seq=2) event captures the failure. This means you can audit not just what was allowed, but whether allowed transactions actually completed.
Enforced Flows
All 10 financial flows are enforced in production:
| Flow | What It Covers |
|---|---|
| Journal Entry | Every double-entry posting to the ledger |
| Fund Transfer | Moving money between operating and reserve funds |
| Bill Payment | Vendor disbursements |
| Invoice Creation | Assessment and fee generation |
| Payment Receipt | Recording incoming payments |
| Year-End Close | Fiscal year finalization |
| Loan Origination | Loan draws, fees, and resident loan origination |
| Loan Payment | Loan payments, payoffs, and prepayment penalties |
| Loan Accrual | Interest accrual, rate resets, and financing-cost amortization |
| Trust Transaction | Trust deposits, disbursements, fee withdrawals, and interest allocation |
Blocking is on for every flow in production. A shadow setting and one switch per flow exist as deploy-time settings; the shadow setting is off and every flow switch is on in production, and no user can change them.
Approval Workflows
Every bill opens an approval request when it is submitted, at any amount. A board member approves it before it can be paid, and that approval is the record the payment spends.
How a bill finds its approvers
A board sets approval tiers: bands of amounts, each naming the levels that must approve a bill in that band. The bands cover every amount and a bill belongs to exactly one, so no two tiers compete for the same bill. Where a board has set no tier, one system default applies — one level, any board member or admin.
Bills are the only document type an approval request is opened for. A workflow recorded against payments, budgets, journal entries or payee changes is marked "Not enforced" on the workflow pages and is never consulted. Fund transfers carry their own approval, on the transfer record itself, at any amount.
Multi-Level Approval
A tier can name more than one level, and each level names who may approve it:
- Approver type: a specific member, a role (treasurer, president, board member, admin), or any board member or admin
- Required approvals: how many approvers are needed at this level
Levels are fixed when the request opens, so changing a tier afterwards does not re-judge a bill already submitted. One person gives at most one approval per request. Authority comes from an accepted membership in that association: a platform staff account with no membership there approves nothing and pays nothing.
Two settings per association govern who may act:
- Self-approval: whether the person who submitted a bill or transfer may also approve it. On by default.
- Separate payer: whether the person who pays must be someone other than the submitter and the approvers. Off by default, and judged when the payment happens.
Each change to a tier or to either setting writes an immutable record naming who made it, and the board is notified. The settings in force are part of the policy snapshot on every enforcement decision.
Status Lifecycle
Approval requests follow a strict state machine:
PENDING → IN_PROGRESS → APPROVED
PENDING → REJECTED
IN_PROGRESS → REJECTED
PENDING/IN_PROGRESS → CANCELLED
No backward transitions. A rejected request cannot be re-opened — a new request must be created.
Authority
Who may act is a property of the association, not of the platform.
- Only the association's admin changes, demotes, or removes a member who holds authority. Platform staff cannot. Staff set up new associations and nothing more, and a staff account with no membership in an association approves nothing and pays nothing there.
- Granting or removing authority takes a fresh authenticator code, and both are emailed to the board.
- Money abilities start off, except for an association's first officer. Paying vendors, vendor bank details, the transfer limit, top-ups, and the payout bank are granted per admin, only by someone who already holds the ability, with a fresh code and an email to the board. An ability that is off is refused on the server, not hidden in the interface. Approving bills, managing members, and changing rules start on, and an admin can be made read-only.
- A resident account holds no ability. No admin role, no seat, no title: nothing to grant and nothing to steal.
- A new association connects a bank only after its signup is approved.
- Assessment schedules leave an audit record on every create and edit, the board is told of any change, and billing every unit takes a fresh code.
Governance → Controls shows how an association is set up, on one page.
A Fresh Code Before Money Moves
Signing in proves who someone was when they signed in. For the actions that send money or decide where it goes, and for the actions that change who holds authority, the person confirms again with the authenticator app, or a recovery code, within the last ten minutes. The list includes paying a vendor by transfer, changing a vendor's bank, changing the transfer limit, pulling money from the association's bank, approving a bill, writing an override, changing a fund policy or the approval workflow, adding or changing the payout bank, changing the bank PIN, giving or removing authority, and billing every unit. Paying several bills in a row needs one confirmation.
- A text-message code never confirms one of these actions.
- A person with no second factor is sent to enroll first.
- A new authenticator cannot confirm a money action until the account's owner approves it from an email sent to the account's address. Rejecting it from that email removes it and locks the account.
The Association's Bank
An association's dues settle into its own connected account at the processor and sweep to its bank. Which bank that is, and who may change it, is controlled.
- One pinned payout bank. Every bank on the association's processor account is recorded with a fixed role, and exactly one is pinned as the payout bank. A vendor's bank can never be the pin; the database refuses it.
- The pin is checked against the processor every hour and on every bank and payout event. If the processor's default bank is not the pin, automatic payouts pause until a person restores the default and releases them.
- A bank PIN that belongs to the association, not to a login. It is checked on the server. Changing it takes the current PIN and a fresh code, five wrong tries lock it, and staff cannot set it.
- Changing the payout bank takes two board members. One asks; a different one confirms with the PIN and a fresh code. The board is emailed, and the first payout to the new bank is held until a person releases it.
- The first payout bank needs payment authority in the association and an authenticator code. A bank that matches a vendor's bank on file is refused.
The controls on a vendor's bank, and on every payment, are in Disbursement Authorization Controls.
Overrides
A guard that can be overridden accepts a recorded board override instead of a block. An override names its scope and is recorded with the resulting enforcement decision. An override of a payment check is bound to the bill and the amount it authorizes and is used up by one posting. Writing one takes the Change rules ability and a fresh authenticator code, and the person who wrote it is judged again when it is spent: someone who no longer holds the ability, or who is barred by the association's own settings from authorizing their own payment, does not authorize it. Overrides appear in audit logs and in evidence packs.
Integrity Findings
The nightly integrity scan reports findings by severity: INFO, WARNING, and CRITICAL. Findings appear in the integrity views and in evidence packs. A finding does not by itself stop a payment. What stops a payment is the guard chain above, and the controls on authority and on the association's bank.
Policy Snapshots
Every governance evaluation references a frozen snapshot of the policy that was active at decision time.
PolicySnapshot captures the exact fund policy configuration (rules, thresholds, constraints) that was evaluated. Each snapshot includes a rules_hash and snapshot_hash for integrity verification.
GovernancePolicySnapshot captures the complete governance policy configuration at the time of the snapshot. Every governance artifact references one.
This prevents "policy ex post facto" arguments — no one can claim a different policy was in effect. The snapshot proves exactly which version of which policy governed each decision.
Each snapshot carries a SHA-256 hash of its content. The fund policy snapshot's verify_integrity() recomputes the hashes and compares them against the stored values, detecting any tampering after creation.
EligibilityRule Evaluation
Declarative eligibility rules can be evaluated for an association on request, through the eligibility API and a backfill command. Nothing in the posting path calls the evaluator: an evaluation reaches the enforcement chain only through exclusions, which the l5_exclusion guard reads. The framework defines nine rules, and 0 are in force in production today.
Rules are:
- Versioned: Multiple versions of the same rule can exist. Only ACTIVE versions are evaluated.
- Immutable once active: Once a rule moves from DRAFT to ACTIVE, its criteria cannot be modified. Changes require a new version.
- Categorized: Rules cover GOVERNANCE, FINANCIAL, COMPLIANCE, VOLUME, TENURE, and COMPOSITE categories.
- Deterministic: Each rule evaluates to ALLOW, DENY, or REFER (manual review required).
An evaluation run on January 15 can be reproduced exactly, using the rule versions that were active on January 15.
Definitions
| Term | Meaning |
|---|---|
| EnforcementDecision | An immutable record of a financial authorization decision, including the outcome, guards evaluated, and timing |
| IntegrityFinding | A detected discrepancy in ledger data, categorized by severity (INFO, WARNING, CRITICAL) |
| Evidence snapshot | A frozen JSON document containing all relevant financial and governance data at a specific point in time |
CARI Integration
Governance control effectiveness is one of five CARI score components, carrying a 25% weight in the composite score. The governance coefficient, which is derived from the telemetry the enforcement layer described here produces, is the primary input to the CARI Governance sub-score.
For published methodology and component weights, see CARI Methodology and Scoring Framework.
This describes production controls as o
How CommunityPay Enforces This