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