Skip to content
CommonLayer
Documentation menu
On this page

Funding model

CommonLayer is funded by contributions that go to projects, not to people. Each project has a treasury with a monthly target and an allocation policy; once a month the treasury pays the maintainer slots whose holders fulfilled their responsibilities, funds shared infrastructure, and keeps a flexible pool for community rewards and a reserve for continuity. This page explains the model as implemented; the precise accounting rules are in the ledger specification.

Funding follows the artifact. Compensation follows the responsibility.

What this page is and is not

  • It describes the code in crates/commonlayer-funding, which is implemented and tested against PostgreSQL (WP-14: 29 unit tests and 58 integration tests, including the nine settlement examples below and a fifteen-step end-to-end scenario; WP-70b added the regression tests of the security review). The acceptance test of workflow B runs the whole composition with the simulated provider's signed webhooks, and the portal's checkout ran end to end in Playwright against the simulator.
  • It is not a promise that money is flowing. As of this writing no production deployment exists, the payment provider defaults to none (contributions are closed), live Stripe payments and Stripe Connect payouts have been tested only against recorded fixtures and a mock server, and real payouts stay disabled until an operator configures a legal entity, confirms tax handling and passes every payout gate.
  • The figures in the examples are the default policy for a $500 target, used to explain the mechanics. They are not statistics.

Principles

  1. A project owns its money. Contributions are credited to the project's treasury. No ledger entry references a maintainer until an obligation is created for a specific month and slot. Replacing a maintainer therefore moves no money.
  2. Compensation is for fulfilled responsibility. It is never paid per vulnerability and never paid automatically: a maintainer slot is paid only when the maintainer's eligibility for the month is finalized as eligible or at_risk. See the maintainer handbook.
  3. Nothing is redistributed silently. Every movement in a monthly settlement is its own line with a plain-language explanation: obligations created or held, forfeitures, vacancies, sweeps, reserve draws.
  4. Funds that are not available cannot be spent. The database refuses any posting that would take an internal account below zero.
  5. Financial records are never edited. Corrections are new, explained transactions.

The treasury

When a project admin configures funding (PUT /api/v1/projects/{slug}/funding/config), the project gets a treasury in one currency (default USD; no currency conversion) with five accounts:

AccountHolds
availableContributions not yet allocated
flexible_poolMoney set aside for community rewards and emergency work
reserveThe continuity reserve
obligationsMoney owed to named people for a specific month or reward, not yet paid
paid_outCumulative compensation paid (a boundary account)

The platform has a shared infrastructure_treasury that receives each project's infrastructure slot and contributions earmarked for infrastructure, and pays recorded infrastructure expenses.

The public funding page of a project shows its target, coverage, the balance of each account, the refundable balance (min(available, unrefunded contributions still inside their refund window)), the reserve runway, the active policy, cycle summaries with every line, and recent contributions.

Allocation policy

A policy is a versioned set of slots that sums exactly to the monthly target (enforced by the database). The default, generated for any target with the largest-remainder method:

SlotKindShareFor a $500 target
primarymaintainer (primary role)40 %$200
secondarymaintainer (secondary role)30 %$150
infrastructureinfrastructure10 %$50
flexibleflexible pool20 %$100

A project admin can create other versions (POST /api/v1/projects/{slug}/funding/policies) and activate one; the active policy is snapshotted when a month's cycle opens. The infrastructure share must be at least funding.min_infrastructure_bps (default 10 %). Policy options include: how slots are sized in a shortfall (pro_rata or priority), where an ineligible maintainer's share goes (reserve or flexible_pool), where a vacant slot's share goes, daily proration of hand-overs, the reserve target in months (default 3), whether the reserve may top up a shortfall, and whether an unused flexible pool is swept to the reserve at the end of the month (default) or carried over.

Contributions

  • Anyone can contribute: a signed-in user, an organization (by a member with org.contributions.create; portal /fund?organization=<slug>), or a guest whose email the payment provider collects. A guest checkout needs an Idempotency-Key of at least 32 characters and returns a status token that lets the guest follow the payment (GET /api/v1/funding/checkout/status with X-Status-Token; portal /fund/complete) without revealing anything about the payer.
  • One contribution can fund several projects and the infrastructure treasury at once (items), one-time or monthly. A monthly contribution creates a new contribution for every successful renewal.
  • is_anonymous hides the contributor's name on every public page. Contribution messages are public if provided.
  • A contribution becomes confirmed only when a verified provider event says the money arrived. Returning from the checkout page proves nothing. Confirmation posts the money to the target accounts and assigns a receipt number (CLR-<year>-<6 digits>, gapless per year).
  • Payment fees are booked separately from the gross amount. By default the fee comes out of the funded accounts (payments.fee_bearer = contribution); an operator can make the infrastructure treasury bear fees instead.
  • Partial payments are booked as received (ADR-0031).

Payment providers

ProviderUseState
none (default)Contributions are closed; every funding write answers payments.not_configuredImplemented
simulatedDevelopment and demos; refuses to start in production; its checkout sends a correctly signed webhook through the real webhook endpointImplemented and used by the tests
stripeCards and SEPA Direct Debit through Checkout Sessions; refunds; fees; payouts through Connect when enabledImplemented; tested with fixtures built from the API reference and a mock server, not against a live account
bank_transferInvoice-paying organizations: instructions with a unique reference; a platform admin records the receiptImplemented; one-time only, refunds and payouts outside the platform

A missing credential never falls back to the simulator.

The monthly cycle

A cycle is one calendar month (UTC) for one project. funding.open_cycles opens the current month and snapshots the active policy. funding.settle_cycles settles a cycle once settlement_delay_days (default 3) have passed after the month and every maintainer evaluation for that month has been finalized. Settlement is computed by a pure function and posted as one ledger transaction:

  1. Sweep an unused flexible pool to the reserve (default policy).
  2. Distributable amount D = min(available, T). If D < T and the policy allows it, draw the difference from the reserve, as far as the reserve goes. This keeps maintainers paid while a temporarily underfunded project's reserve lasts.
  3. Size the slots: full amounts if D = T, otherwise pro rata (or in priority order).
  4. Split each maintainer slot across the people who held it during the month, by day. Each part becomes an obligation (pending if eligible or at risk; held if under review or not finalized) or is forfeited to the reserve or the flexible pool (ineligible, temporarily suspended). Days without a maintainer are a vacancy line.
  5. The flexible slot goes to the flexible pool; the infrastructure slot to the infrastructure treasury; a reserve slot to the reserve.
  6. If money remains and the reserve is below its target, the surplus tops up the reserve; anything beyond that stays in available for the next month.

Worked examples

All with the default policy and a $500 target (T = 50000 minor units). These are literal test cases (crates/commonlayer-funding/src/domain/settlement.rs, example_1 to example_9).

#SituationResult
1$500 available, both maintainers eligibleprimary $200 and secondary $150 owed; $100 to the flexible pool; $50 to infrastructure; nothing left
2As 1, secondary ineligible, policy sends forfeitures to the reservesecondary's $150 goes to the reserve as a forfeited_ineligible line naming the evaluation; no obligation for them
3$300 available, no reservepro rata: $120 / $90 / $60 flexible / $30 infrastructure; coverage 60 %
4$300 available, $500 reserve, draws allowed$200 drawn from the reserve; full allocation; reserve left $300
5$500 available; primary X for days 1–10 of a 30-day month, then YX $66.67, Y $133.33 (both eligible)
6$800 available, no reserve, reserve target 3 monthsfull allocation; the $300 surplus goes to the reserve
7Primary slot vacant all month$200 to the reserve as a vacant line
8Secondary under review$150 moved to obligations as held; paid or released after the decision
9Treasury holds $10,000; the primary steps down and a replacement is approvedno ledger transaction; every balance unchanged; the next settlement pays the replacement

Funding state and continuity

Each project has a public funding state computed from coverage = (committed monthly recurring + one-time inflow this month + carry-over at month start) × 10000 / target in basis points: surplus from 125 %, fully_funded from 100 %, partially_funded from 50 %, underfunded below that; not_configured without an active policy; paused when the project admin paused contributions. The thresholds are runtime settings (funding.surplus_bps, funding.fully_funded_bps, funding.partially_funded_bps). The reserve runway (reserve / target, in months) is shown next to it.

Consequences are deliberately slow. A project's lifecycle becomes underfunded only after two consecutive underfunded settled cycles (runtime setting funding.underfunded_notice_cycles) and a runway below one month (configuration). Before that maintainers and followers get shortfall notifications, and commitments continue. The funding state is separate from the security state and the maintenance state: a well-funded project can have overdue vulnerabilities, and an unfunded one can be clean.

Refunds and disputes

  • A contributor (or a platform admin) can request a refund within the refund window (default 14 days after confirmation, runtime setting funding.refund_window_days), up to what the target accounts still hold minus refunds already pending. Money already allocated to maintainers is not refundable through the platform (funding.refund_not_available). A requested refund holds what it will take until the provider confirms or refuses it, so concurrent requests cannot together take more than the accounts hold. The ledger changes when the provider confirms the refund.
  • Refunds and disputes of subscription payments reach the ledger: Stripe invoice payments are linked to their PaymentIntent and charge (security review HIGH-3; tested against a mock of Stripe, not a live account).
  • A dispute (chargeback) is not optional: the provider takes the money. The platform books it from the project in a fixed order: available, then reserve, then flexible_pool, then the shared infrastructure treasury. Reaching the infrastructure treasury, or an amount no account can cover, is recorded as an administrative alert; the ledger never goes negative. A won dispute returns every step to its source.

Paying people

Obligations are paid by payout runs, which a platform admin plans and a second platform admin approves. A dry run is always available and shows who would be paid, how much, and why each skipped obligation is skipped. Real execution requires every gate (ADR-0032): live mode configured, a provider with payouts, a configured legal entity, confirmed tax handling, a clean reconciliation less than 24 hours old, a second approver, and for each payee a verified payout destination and tax status. If any gate fails, nothing moves and the failed gates are listed. Approving a run needs a person in an interactive session; an API token or a service account cannot approve.

A payout whose provider outcome is ambiguous (a timeout, a 5xx, 409 or 429 answer) is recorded as unknown, its obligation stays scheduled, and it is retried only with the same provider idempotency key; an administrator resolves it after checking the provider (ADR-0041). The destination is fixed when the run is planned.

No bank details are stored by CommonLayer; maintainers' payout destinations are provider references.

Community rewards

Money for community contributors comes only from the flexible pool. A maintainer proposes a reward for an approved community contribution; up to funding.reward_single_approval_max_minor (default $100, a runtime setting) one approval suffices, above it two, one of them from a project or platform admin. Nobody approves a reward for themselves, the proposer never approves (not even a single-approval reward), approvers are people (never service accounts), and a contribution carries at most one open or funded reward (ADR-0045). Final approval moves the amount from the flexible pool to obligations at once; an insufficient pool refuses the reward (funding.insufficient_flexible_pool) and takes nothing from other accounts. Pull requests are never rewarded automatically. See the contributor handbook.

Reconciliation and transparency

An hourly job checks that every balance equals the sum of its entries, that all balances sum to zero, that each project's obligations account equals its open obligations, that contribution and payout states agree with the ledger, that no provider event is stuck, and, for providers that report balances (Stripe), that internal balances match what the provider holds. A mismatch is recorded, alerts operators and blocks payouts.

Public pages show balances per account kind, policies, cycle lines with explanations, contribution amounts with display names (unless anonymous), the project ledger without private metadata, and the infrastructure treasury's allocations and expenses. What is never public is listed in privacy rules.