Eligibility Evaluation Framework

Declarative eligibility rules with versioned criteria, deterministic evaluation, immutable evaluation records, and three rule actions that combine into six results. The framework fails closed, so a rule that cannot see its signals withholds a verdict instead of passing.

9 min read Governance & Controls Published Dec 9, 2024

CommunityPay evaluates an association's eligibility with a declarative rule framework. Rules are defined as criteria, not code. They are versioned, immutable once active, and produce deterministic results. This page describes the rule architecture, the evaluation process, and the audit artifacts produced.

Design Principles

Declarative, Not Procedural

Eligibility rules are stored as data. A criterion names a signal, an operator, and a value:

{"signal": "reserve_funding_ratio", "op": ">=", "value": 0.10}

Criteria can nest AND and OR groups. Because they are data and not application code:

  • Rules can be inspected without reading source code
  • Rule changes are versioned and auditable
  • The same rule engine evaluates all rules uniformly
  • Historical evaluations can be reconstructed from the stored criteria and signal values

Each rule links to the legal authority that permits it. When an authorizing statute changes, the rule is flagged for legal review.

Deterministic

Given the same rule version and the same signal values, the evaluation always produces the same result. There is no randomness, no external state dependency, and no side effects.

This is critical for audit. An evaluation performed on January 15 can be reproduced exactly by loading the rule versions active on January 15 and the signal values captured at evaluation time.

Fail Closed

An evaluation reaches ELIGIBLE only from rules that ran on signals that were actually obtained. An empty rule set, a rule whose signals cannot be resolved, and a trigger that could not be evaluated all produce WITHHELD. WITHHELD is not a refusal. It is the engine stating that it has no basis to judge, and every surface that reads it treats it as not evaluated, never as a pass.

Rule Structure

Each eligibility rule defines:

Field Purpose
rule_id Stable identifier across versions
code Machine-readable code (e.g., TENURE_6M, RESERVE_RATIO_MIN)
criteria JSON expression referencing signal values
criteria_hash SHA-256 of the criteria, checked on every save
category GOVERNANCE, FINANCIAL, COMPLIANCE, VOLUME, TENURE, or COMPOSITE
action ALLOW, DENY, or REFER (see below)
priority Evaluation order (lower = earlier)
signal_namespace TELEMETRY, RISK_SIGNAL, or UNDERWRITING
status DRAFT, ACTIVE, DEPRECATED, or ARCHIVED
version Integer version, incrementing
parent_version Foreign key to the version this one was derived from
effective_date When this version becomes applicable (optional)
end_date When this version stops being evaluated (optional)

Rule Actions

Action Behavior
ALLOW The criteria must hold. A rule that does not pass makes the entity INELIGIBLE.
DENY A rule that does not pass makes the entity INELIGIBLE.
REFER A rule whose criteria pass sends the evaluation to a person. The result is CONDITIONAL.

A REFER rule is how a tier above a floor is written: below a tenth funded is a refusal, and below a quarter funded is a referral.

Results

Result Meaning
ELIGIBLE Every rule ran, every signal was obtained, and every rule passed
INELIGIBLE A rule that must pass did not
CONDITIONAL A REFER rule fired, or a trigger opened a review exclusion; a person must review
EXCLUDED An active exclusion blocks the entity, or a trigger opened a block exclusion
WITHHELD There is no basis to judge: no rule in force, a signal could not be obtained, or a trigger could not be evaluated
ERROR The evaluation itself raised an error

Installed Rules

The framework defines nine rules. 0 are in force in production today. A rule is active only when every signal its criteria read has a live writer; a rule whose signal nothing writes is held. A held rule is written down, versioned, hashed, and linked to its statute, and it decides nothing.

Tenure Rules

Code Criteria Action Description
TENURE_6M months_on_platform >= 6 ALLOW Refuses an association live on the platform for less than six complete months
TENURE_12M months_on_platform < 12 REFER Refers an association with less than a year to review: six to twelve months is a referral, under six is a refusal

Financial Rules

Code Criteria Action Description
RESERVE_RATIO_MIN reserve_funding_ratio >= 0.10 ALLOW Refuses an association funded below a tenth of the target its reserve study set; withheld where no reserve fund carries a target
RESERVE_RATIO_HEALTHY reserve_funding_ratio < 0.25 REFER Refers an association funded below a quarter of its target to review
OPERATING_RATIO operating_expense_ratio <= 1.10 ALLOW Refuses an association whose trailing-year expenses exceed its revenue by more than a tenth; withheld for a year with no revenue posted
DELINQUENCY_MAX assessment_delinquency_rate <= 0.15 ALLOW Refuses an association whose past-due share of its open receivable exceeds fifteen percent

Governance Rules

Code Criteria Action Description
GOV_SCORE_MIN governance_risk_tier in [LOW, MEDIUM] ALLOW Refuses an association whose governance risk tier is HIGH or CRITICAL
BOARD_ATTESTATION board_attestation_current == True ALLOW Requires a board attestation signed within the last year; withheld, not failed, for a board never asked to attest
QUORUM_COMPLIANCE quorum_compliance_rate >= 0.75 ALLOW Held: there is no meeting or quorum record, so nothing writes the signal and the rule decides nothing

Signals

Rules reference signals, which are named data points registered in the signal registry with a source and a declared type. A rule can reference only a registered signal; a criterion that names an unknown signal does not save. The registry, its namespaces, and how signals are captured with each decision are described in Enforcement Dispatcher & Guard Architecture.

Activation asks one more question: does anything write each signal the rule reads? A signal nothing produces would leave the rule permanently indeterminate, and a signal with a writer that means something other than what the rule reads is refused as contested. Either way the rule is installed and held, with the reason written on the row. There is no flag to override it.

Evaluation Process

When eligibility is evaluated, the following sequence executes:

1. Gather Signals

The signal values for the entity are gathered into a flat dictionary. A signal that cannot be obtained, or that is present with a null value, is recorded as unavailable.

2. Check Existing Exclusions

If an active exclusion blocks the entity, the result is EXCLUDED and no further evaluation runs.

3. Evaluate Triggers

Exclusion triggers are evaluated and every trigger is recorded. A trigger that opens a block exclusion makes the result EXCLUDED. A trigger that opens a review exclusion makes the result CONDITIONAL and skips the rules. A trigger that could not see a signal it needs has not cleared the entity.

4. Evaluate Each Rule

Active rules are evaluated in priority order, filtered by product, entity type, and effective dates. Each rule's criteria are evaluated against the gathered signals. The outcome is PASS, FAIL, or indeterminate: a rule that could not obtain its signals, or whose criteria could not be evaluated, has no verdict and is never read as a failure.

5. Combine

The result is chosen least-favourable-first:

  • A rule that must pass did not: INELIGIBLE
  • A REFER rule fired: CONDITIONAL
  • A rule or trigger was indeterminate, or no rule produced a determination, or no rule is in force: WITHHELD
  • Every rule ran and passed: ELIGIBLE

A definite refusal outranks a referral, which outranks a withheld verdict, which outranks a pass. Withholding never softens a refusal: a rule that failed on a signal that was obtained is INELIGIBLE whatever else was missing.

6. Create the Evaluation Record

An EligibilityEvaluation is created with:

  • A reference to every rule evaluated: rule id, version, code, and criteria hash, with a snapshot of the criteria
  • All signal values gathered, and the list of signals unavailable
  • Per-rule results and the combined result, with the reason
  • The active exclusions and trigger hits that affected it
  • A fingerprint of the rules and triggers in force, the evaluation date, and what triggered the evaluation

The model refuses to update an existing record and refuses to delete one.

Rule Versioning

Why Versioning Matters

Consider this scenario: On January 1, the RESERVE_RATIO_MIN rule requires a 10% reserve ratio. On March 1, the requirement is updated to 15%.

Without rule versioning, an auditor reviewing a January evaluation would see the 15% rule and might incorrectly conclude that the entity was evaluated against a 15% threshold. With versioning, the January evaluation references the exact rule version that was active on January 1 — the 10% version.

How Versioning Works

  • Each rule has a stable rule_id, an integer version, and a parent_version foreign key to the version it was derived from
  • The criteria of an ACTIVE rule cannot be changed: saving different criteria raises an error, and a new version is created instead
  • A new version starts as a DRAFT with the version number incremented
  • Two ACTIVE versions of one rule cannot cover overlapping dates
  • effective_date and end_date define the temporal scope

Each evaluation records the rule id, version, and criteria hash of every rule it ran, and a snapshot of the criteria, so even if a rule code is reused across versions, the evaluation references the exact version.

Where Evaluations Come From

An evaluation runs when it is requested: through the authenticated eligibility endpoint for an association, and by the backfill run that initializes exclusion state. The verdict is written as an immutable EligibilityEvaluation.

Evaluations reach the enforcement chain through exclusions. When an evaluation opens a block exclusion, the l5_exclusion guard refuses every posting for that association while the exclusion is active, and records the refusal in the EnforcementDecision. That guard applies to all flows. The separate fund_eligibility guard answers a different question: whether an expense account posting to a reserve fund is marked reserve-eligible.

What This System Proves

The eligibility evaluation framework ensures that:

  1. Rules are transparent: Any auditor can inspect the rule criteria — they are data, not buried in code
  2. Evaluations are reproducible: The same rule version plus the same signals always produce the same result
  3. History is preserved: Every evaluation is stored as an immutable artifact with complete context
  4. Changes are traceable: Rule version chains show exactly when requirements changed and who changed them
  5. Absence is not a pass: A rule that cannot see its signals withholds a verdict, so a gap in the data never reads as a clean result

This describes prod

How CommunityPay Enforces This
  • Rules are declarative JSON criteria evaluated against signal values — not hardcoded logic
  • Three rule actions (ALLOW, DENY, REFER) combine into six results (ELIGIBLE, INELIGIBLE, CONDITIONAL, EXCLUDED, WITHHELD, ERROR)
  • The framework fails closed — a rule whose signals cannot be obtained is withheld, never passed, and a rule whose signals nothing writes is held and decides nothing
  • Rule criteria are immutable once ACTIVE — changes require a new version that points to its parent
  • Every evaluation produces an immutable EligibilityEvaluation artifact capturing the rule versions, criteria hashes, signal values, and unavailable signals at evaluation time
Login