Documentation › Concepts
Contributor reputation
Steward keeps a per-domain reputation score for every contributor. The score is what decides whether a PR author can self-merge, who Steward suggests as a reviewer, and how heavily the system weights an author's voice in cross-domain consensus. This doc explains how reputation is earned, how it decays, and where to look at it.
The shape
Reputation is a vector, not a single number. Each
contributor has a separate score per domain from
governance.pxf:
alice: { api: 12.4, ui: 0.8, docs: 6.1, rest: 2.0 }
bob: { api: 1.2, ui: 4.7, docs: 0.3, rest: 1.1 }
A new PR is scored against the domains its files map to (see
governance.pxf + domains).
Alice has authority in api (12.4); Bob does not. If a PR
touches api, Alice can self-merge under most thresholds; Bob
needs a review from someone above the bar.
The vector is exposed by ReputationService.Get on the API and
under the Contributors surface in the dashboard.
How it's earned
Every merged PR appends a PR_MERGED event to the ledger. The
event carries a domain + a point delta computed by:
earned = base × (1 + ln(complexity)) × criticality
Where:
| Variable | Source |
|---|---|
base |
decay.base_points in governance.pxf (typical: 1.0). |
complexity |
The PR's cyclomatic complexity delta. Clamped to ≥ 1 — trivial PRs still earn base. |
criticality |
The touched domain's criticality_multiplier. A bump in a sensitive domain weighs more than the same change in a low-risk one. |
So a small fix in docs earns less than a substantive change in
auth, which is the intent.
How it decays
Reputation decays exponentially with a half-life set by
decay.half_life in governance.pxf. The math is plain
continuous decay:
R(t) = R₀ × e^(-λt)
where λ = ln(2) / half_life_in_days
A typical half_life: "180d" (six months) means an inactive
contributor's reputation halves every six months. After a year
they're at 25%; after two years, 6.25%.
Decay is computed on lookup, not stored. Steward replays the
ledger up to "now," applying decay between events, and returns
the current value. The ledger is the source of truth — the
current number is always derivable. Periodic DECAY_APPLIED
checkpoints get written for audit-trail purposes (so an auditor
can reconstruct the curve without trusting the engine), but the
math doesn't depend on them.
Leave windows pause decay
A contributor can mark themselves as on leave (leave_until on
the contributor row). Decay is paused for the period
[from, leave_until]. So a maintainer on parental leave doesn't
return to a halved reputation just because they took six months
off — the elapsed time inside the leave window is excluded from
the decay calculation.
How it's gated
Three places governance.pxf reads from reputation:
Approval threshold (self-merge gate)
{ name: "core", path_globs: ["internal/**"], approval_threshold: 2.0 }
When the PR author's reputation in the touched domain is ≥
approval_threshold, they can self-merge. Below, the gate
requires a review from someone above the threshold. This is
reputation-weighted, not seat-count: a first-time external
contributor with core: 0 always needs a review, regardless of
whether they're listed as a repo collaborator on GitHub.
Supermajority ratio (cross-domain consensus)
When a PR spans multiple domains, the cross-domain consensus
engine (ADR-004) requires that authors approving the PR hold a
supermajority — by reputation — across all touched domains. The
supermajority_ratio field tunes that fraction. Default-off:
most repos run with single-domain approval thresholds.
Amendment voting
Changes to governance.pxf itself can require reputation-
weighted votes from the top-N reputation holders (the
amendments section). The "governance-of-governance" hatch.
Day-one repos leave it off; large projects flip it on once any
one merging an amendment feels coercive.
Founders
A new repository's reputation vectors are empty by default —
every contributor starts at zero. That makes the day-one
approval_threshold unreachable until someone earns points,
which means every PR needs a review from someone who already
has reputation, which means no one can ever get the first
PR through. Chicken-and-egg.
The way out: founders. The founders section of
governance.pxf seeds reputation:
founders: [
{ handle: "alice", reputation_seed: { api: 10.0, core: 10.0, docs: 5.0 } }
{ handle: "bob", reputation_seed: { ui: 8.0, docs: 5.0 } }
]
On first ledger boot, Steward emits a FOUNDER_SEED event for
each founder. From there, the normal earn/decay loop takes over.
Founders don't get special treatment afterwards — their
reputation decays at the same rate as everyone else's. The seed
is bootstrap-only.
Slashing (retroactive penalty)
If a merged PR is later linked to a critical bug, Steward
can retroactively reduce the author's reputation in that domain
via a PENALTY_BUG event. Configured by the slashing section:
slashing: {
enabled: true
base_points: 2.0
growth_half_life: "30d"
max_multiplier: 3.0
}
The penalty starts at base_points and grows over time
(half-life-controlled), reflecting "the longer the bug was
latent, the more it hurt the project." Capped at
base_points × max_multiplier. Disabled by default — flip it
on after you've calibrated to your bug-tracking practice; a
team without a stable "this was a critical bug" signal will
mis-fire it.
Reputation is global across repos
For the same domain name, a contributor's reputation
accumulates across every repo that uses it. Alice's core
reputation is the sum of her core earnings in every repo whose
governance.pxf declares a domain named core. The intent: a
senior Go engineer's authority on the core domain shouldn't
restart at zero just because they opened their first PR on a new
repo.
The flip side: domain names are a coordination point across
repos. Two unrelated projects both using core will share the
same reputation pool. If you don't want that, pick a more
specific name (steward-core, acme-api).
Where to look at it
| Surface | What it shows |
|---|---|
| PR comment | Author's reputation in each touched domain, inline with the verdict. |
/app/contributors |
Per-contributor reputation vector across all domains. Filter by domain or status. |
/app/repos/<id>/contributors |
Per-repo view — only contributors active on this repo, only domains declared on this repo's governance.pxf. |
/api/.../ReputationService/Get |
The same data via the read-only API; repos:read scope. See the API reference. |
The contributor view shows both the current decayed reputation
and the recent ledger events that produced it — so a maintainer
can answer "why is Alice's core rep 8.3, not 10?" by reading
the actual event chain.
What's next
- Gates: DCO, CLA, complexity — the
other PR gates that work alongside
approval_threshold. - governance.pxf + domains — the file that defines half-life, founders, slashing, and the per-domain thresholds reputation gates against.
- The fleet dashboard — the per-repo "who's contributing where" view.
Steward