On this page
Maintainer handbook
This handbook is for people who maintain a CommonLayer project or want to: how to apply,
how assignments work, what you are responsible for, the security deadlines, the periodic
tasks, how your monthly eligibility is evaluated and explained, how to appeal, what
happens when you step down or are replaced, and how compensation reaches you. It
describes the implementation in crates/commonlayer-maintenance and
crates/commonlayer-funding. Each step names the portal page and the API operation
behind it; the few steps that have no portal page yet say so. The pages were exercised by
the Playwright suite against the development seed (locally); no maintainer has used them
on a deployed platform.
The short version
- You maintain a project, not a sum of money. The project's treasury owns the money; you hold an assignment to a slot of its allocation policy.
- Each month the platform evaluates whether you fulfilled your responsibilities, from
facts it records (security deadlines, tasks, releases, reviews). The result is
eligible,at_risk,under_review,temporarily_suspendedorineligible, with an explanation in plain language. - You are paid for the month if the final state is
eligibleorat_risk. You are never paid per vulnerability, and nothing is paid automatically: payouts go through a gated, dual-approved payout run. - Every decision about you can be appealed, and nobody can decide about themselves.
Becoming a maintainer
- Apply on Dashboard → Maintainer → Apply (
/dashboard/maintainer/apply,POST /api/v1/maintainers/apply): a public bio and links, and acceptance of the maintenance agreement. The page shows the agreement's full text, version and SHA-256 (GET /api/v1/maintenance/agreement). Edit your profile later on/dashboard/maintainer/profile(GET/PATCH /api/v1/users/me/maintainer). - A platform admin approves or rejects the application on
/admin/maintainers(platform.maintainers.approve, a person in an interactive session). Nobody can approve their own application (databaseCHECK). - Approved maintainers appear in the public directory (
GET /api/v1/maintainers) with their public bio, links and assignments. Payout destination and tax status are never public.
Assignments
An assignment binds you to a project in a role:
| Role | Slot | Responsibilities (default) |
|---|---|---|
primary | primary (paid) | all ten |
secondary | secondary (paid) | all ten |
additional, temporary | as the policy defines | as assigned |
security_reviewer | — | security monitoring, acknowledgement, vulnerability triage, remediation coordination |
The ten responsibilities are security_monitoring, security_acknowledgement,
vulnerability_triage, remediation_coordination, pull_request_review,
dependency_updates, pipeline_maintenance, release_publication, artifact_integrity
and documentation. Eligibility rules apply only to the responsibilities your assignment
holds.
- A project admin proposes an assignment on the project's Maintainers page
(
/dashboard/projects/{slug}/maintainers,POST /api/v1/projects/{slug}/assignments,project.maintainers.manage). It becomes active when a platform admin approves it on/admin/maintainers(POST /api/v1/assignments/{id}/approve, interactive session), or immediately if the proposer is a platform admin. - The database guarantees one active primary and one active secondary per project, and that no slot is held by two assignments at the same time.
- Normal funded maintenance needs both a primary and a secondary. Fewer is a
maintenance-health degradation that shows on the project (lifecycle
maintenance_degraded;unmaintainedafter 30 days without either), not an error. - Assignments start in the current month at the earliest, so settled months never change retroactively.
- Your active assignment gives you the project role
maintainer; asecurity_reviewerassignment gives thesecurity_reviewerrole.
Security duties and deadlines
Findings are opened by the security domain when a scan of a currently supported digest
finds a vulnerability that no not_affected statement covers. Each finding has an
acknowledgement and a remediation deadline from the project's SLA policy (or the platform
default):
| Effective severity | Acknowledge within | Remediate within |
|---|---|---|
| Critical | 24 hours | 7 days |
| High | 48 hours | 14 days |
| Medium | 7 days | 30 days |
| Low, negligible, unknown | 14 days | end of the calendar month after the month it opened |
These are policy defaults, not guarantees. A vulnerability in CISA's KEV catalogue is
raised to at least high. A project admin can publish a project SLA version
(PUT /api/v1/projects/{slug}/sla); the policy in force when a finding opens is
snapshotted on it.
What you do with a finding (project.security.triage), on Dashboard → Maintainer →
Findings (/dashboard/maintainer/findings, soonest deadline first) or on the project's
security page (/dashboard/projects/{slug}/security):
- Acknowledge it (
POST /api/v1/findings/{id}/acknowledge). Assessing, requesting an exception or starting remediation also acknowledges. - Assess applicability:
affected, ornot_affectedwith a VEX justification (component_not_present,vulnerable_code_not_present,vulnerable_code_not_in_execute_path,vulnerable_code_cannot_be_controlled_by_adversary,inline_mitigations_already_exist). For a finding below the confirmation threshold,not_affectedcloses the finding and is published in the project's OpenVEX document. - Second reviewer. For a finding whose effective severity is at or above
security.not_affected_confirmation_min_severity(defaulthigh, a runtime setting; KEV escalation counts), yournot_affectedstatement is pending: the finding stays acknowledged, its deadlines keep running, the scan gate still blocks, and nothing is published until another person with triage rights on the project confirms or rejects it (pending statements are listed on the project security page and the findings page;POST /api/v1/security/assessments/{id}/confirmor…/rejectwith a note). You can never confirm your own statement; a service account cannot confirm at all. The same applies to statements imported with an OpenVEX document. A project with a single active maintainer needs a second person with triage rights, for example asecurity_reviewerassignment (ADR-0037). - Request an exception when the deadline cannot be met (no upstream fix, alternative
mitigation, coordinated disclosure, external dependency, risk accepted, not applicable)
with a justification, an expiry and a new remediation date. Someone else with
project.security.exceptions.decidedecides it on/dashboard/projects/{slug}/exceptions(a person, never a service account); the database refuses self-decision. The same page lists every exception with its status and decider. While approved, the extended date applies. Exceptions are public with their justification unless embargoed. - Remediate by releasing a fixed digest. A finding resolves when every supported version line's current digest has a fresh scan without the vulnerability. The old digest keeps its record forever: a fix never rewrites history.
Breaches are permanent facts. Acknowledging late does not clear the breach; a reopened finding starts a new episode with new deadlines (ADR-0029). Private security reports to your project must be acknowledged within the critical acknowledgement window.
Periodic tasks
maintenance.generate_tasks (daily) creates the required tasks of each period:
| Task | Cadence |
|---|---|
scan_triage | weekly |
dependency_update | monthly |
pipeline_health | monthly |
docs_review | quarterly |
release_publish | when upstream is ahead of the latest release of a supported line |
pr_review | per tracked community pull request waiting beyond the review window |
security_ack | per finding (created when the finding opens; completed when it is acknowledged) |
Complete a task with evidence (links, notes, a private flag) on Dashboard → Maintainer →
Tasks (/dashboard/maintainer/tasks, POST /api/v1/tasks/{id}/complete); the page also
shows your closed tasks. If a task does not apply, request a waiver with a reason on the
same page (POST /api/v1/tasks/{id}/waive); a project admin other than you, a person,
decides it. Deciding a waiver has no portal page yet: the project admin sends
POST /api/v1/tasks/{id}/waive with a decision. Open tasks past their due date are
marked missed. The project's public maintenance page (/projects/{slug}/maintenance)
lists tasks with their status and the evidence links you did not mark private; waiver
reasons, private evidence and security-acknowledgement tasks are never shown there.
How eligibility is evaluated
Once a month, after the month ends, maintenance.evaluate_eligibility computes a
preliminary evaluation for every assignment that held at least one day of the month.
Rules
Each rule yields pass, warn, fail or not_applicable with a sentence and references
to the evidence. Default thresholds are in maintenance.eligibility configuration.
| Rule (responsibility) | Fails when | Warns when |
|---|---|---|
sla.acknowledgement (security_acknowledgement) | a critical or high finding was acknowledged late or not at all | more than 1 medium or more than 2 low late acknowledgements |
sla.remediation (remediation_coordination) | a critical or high remediation deadline passed in the month without resolution or approved exception | the same for medium (more than 1) or low (more than 2) |
sla.outstanding (remediation_coordination) | a critical or high finding is overdue at month end | a medium or low finding is overdue at month end |
reports.acknowledgement (security_acknowledgement) | a private security report was not acknowledged in time | — |
tasks.required (per task kind) | 2 or more required tasks missed | 1 missed |
release.activity (release_publication) | no successful build for more than 45 days while upstream moved, or upstream lag over 30 days in a supported line | upstream lag over 14 days |
reviews.responsiveness (pull_request_review) | — | community pull requests waiting more than 7 days for a first review exceed the tolerance |
agreement.accepted | — | a published agreement is not accepted (the state becomes under_review) |
security_ack tasks do not count in tasks.required, because sla.acknowledgement
already judges the same deadline.
Time scoping. A deadline, report or task counts only if it fell due while your
assignment held the slot. The end-of-month rules (sla.outstanding, release.activity,
reviews.responsiveness) apply only if your assignment is still active at month end. If
you hand over mid-month, you are not blamed for what happens after; your successor is not
blamed for what happened before.
Who wrote the fix does not matter. If a community contributor wrote the fix and you reviewed, released and verified it in time, you met the obligation.
State
Precedence, top to bottom:
- An active suspension overlapping the month →
temporarily_suspended. - An open appeal, a missing agreement, or an input that could not be loaded →
under_review. - Any
fail→ineligible. - Any
warn→at_risk(still paid; you are notified). - Otherwise
eligible.
Explanation
Every evaluation stores a plain-language explanation generated from the rule results,
for example "Ineligible for 2026-10: CVE-2026-1234 (critical) was acknowledged 32 h after
detection; the limit is 24 h." Hours and days are rounded up, so a limit is never
understated. A public variant without private identifiers appears on the project page; the
full detail is visible to you and to holders of project.read_private.
The inputs, thresholds and rule-set version are stored with the evaluation, so it can be re-derived exactly later (ADR-0033).
Timeline of a month
- After the month ends, preliminary evaluations are stored and you are notified
(
maintenance.eligibility.decided). - For
maintenance.review_window_days(default 2) you can add evidence or complete documentation; evaluations are recomputed when evidence changes. maintenance.finalize_eligibility(daily) finalizes them. If an input could not be loaded, finalization waits up to seven more days, then finalizes asunder_review.- Settlement reads only finalized evaluations. A missing or
under_reviewevaluation results in a held obligation, never a payment and never a forfeiture.
Appeals and overrides
- Your evaluations, with their explanations and rule results, are on Dashboard →
Maintainer → Eligibility (
/dashboard/maintainer/eligibility). You may appeal a finalized evaluation withinmaintenance.appeal_window_days(default 14) from its detail page (/dashboard/maintainer/eligibility/{id},POST /api/v1/eligibility/{id}/appeals). An open appeal sets the state tounder_review. - A platform admin with
platform.eligibility.overridewho is not you resolves it on/admin/eligibility, in an interactive session, with a justification of at least 10 characters. Upheld:eligible(orat_risk); denied: the previous state. - A platform admin can override a state with a mandatory justification; the computed state is kept next to the override, and overrides are refused while an appeal is open.
- When a finalized state changes,
maintenance.eligibility.changedis published and the funding domain releases or reinstates the money: an upheld appeal after a forfeiture reinstates the amount from the account that received it, as far as that account still holds it, and any shortfall is recorded and shown to administrators. - Every decision, override and appeal resolution is audited.
Stepping down and replacement
A project admin ends an assignment (POST /api/v1/assignments/{id}/terminate) or replaces
the maintainer (POST /api/v1/assignments/{id}/replace) on the project's Maintainers
page, which shows the actual treasury balances next to the statement that a replacement
does not touch them:
- The old assignment ends at the effective time (
replacedorstepped_down). - A new assignment for the same role and slot starts at that time, linked to the old one, pending platform approval unless the actor is a platform admin.
- Both are recorded in the assignment history (public), audited, and announced.
The replacement method has no access to the ledger. A test asserts that every balance, transaction and obligation is identical before and after a replacement on a treasury of $10,000; your past obligations and payouts are untouched. The next settlement splits the slot by day between you and your successor (if both are eligible for their days). If the replacement is approved in a later month, the slot is vacant until then.
Platform admins can suspend a maintainer (platform-wide or for one project) and lift the
suspension on /admin/maintainers, which also lists suspensions in force and past;
nobody can suspend or lift a suspension of themselves.
Compensation
- Settlement (after the month, once evaluations are final) creates an obligation for
your share of the slot:
pendingif eligible or at risk,heldif under review. See the funding model for how shortfalls, the reserve and hand-overs are handled. - Payout runs pay pending obligations. You need a verified payout destination (a
reference at the payment provider; CommonLayer stores no bank details) and a tax status
on file, recorded by a platform administrator on
/admin/maintainers(PUT /api/v1/admin/maintainers/{user_id}/payout-status). Without them your obligation is skipped and stays owed. The destination is fixed when a payout run is planned. - Real payouts happen only when the operator has configured live payouts and passed every payout gate, including a second platform admin's approval. As of this writing no live payouts are configured anywhere.
- Your own obligations and payouts: Dashboard → Maintainer → Compensation
(
/dashboard/maintainer/compensation,GET /api/v1/users/me/compensation).
Community contributions you review
Contributions (pull requests, reviews, security fixes, documentation) are recorded from
tracked GitHub pull requests (merged pull requests by non-bot authors of a linked
repository) or submitted by hand. You review them (POST /api/v1/contributions/{id}/review,
project.contributions.review, a person, never a service account) and approve or reject
with a note; you cannot review your own. Reviewing contributions has no portal page
yet; use the API with a session or a personal API token. Approval grants the
contributor the contributor role on the project. Reward proposals are made on
Dashboard → Maintainer → Proposals (/dashboard/maintainer/proposals) or the project's
Rewards page; the pages explain before you click that you can never approve a reward
you proposed. Approved contributions are the only basis
for a community reward from the flexible pool; see the
contributor handbook.
Your dashboard
Dashboard → Maintainer (/dashboard/maintainer,
GET /api/v1/users/me/maintainer/dashboard) shows your status, suspensions, the agreement
warning, counts of what needs attention, your assigned projects with their three states,
and recent evaluations.