Skip to content

Latest commit

 

History

History
253 lines (198 loc) · 14 KB

File metadata and controls

253 lines (198 loc) · 14 KB

Eligibility Pipeline

Eligibility asks whether OSE can actually fund a project. Four independent checks answer it: the project carries an open-source license, it has shown some intent to be funded, no company is strongly affiliated with it, and it is still alive. A repo is eligible only when all four pass — there are no weights and no trade-offs, so any single failure is disqualifying.

The stage flags; it never drops. An ineligible repo keeps its row in data/eligibility/eligibility.csv, with eligible=False and the failing check both visible, and data/preview/repos.csv holds the same population. Row counts stay identical across the stages, so filtering is a downstream choice — filter on eligible when you want candidates only.

Every check is automated. A builder computes each one from fetched sources plus the curated override files in data/eligibility/. This is the last stage that scores anything: preview and health run afterwards but change no verdict. The README summarizes all three stages in one table.

What the stage produces

Four files in data/eligibility/, one builder each. Coverage counts live in the preview pipeline sheet.

File Builder Columns
licenses.csv src.eligibility.build_licenses repo, repo_id, license, license_source (override/registry/github/gitlab/""), oss
funding.csv src.eligibility.build_funding the funding signals + score, intent, nonprofit — schema in components/funding.md
active.csv src.eligibility.build_active repo, repo_id, eol, archived, active
eligibility.csv src.eligibility.build_eligibility repo, repo_id, oss, intent, nonprofit, active, eligible, value_comps, risk_comps, complete

Scope — the top-repo set

Input is the top-repo set — valid class-A repos from data/value/value.csv (settings.json top_repos: classes = ["A"], platforms = ["github", "gitlab"], git_valid == True; risk_input.value_classes names the same scope), loaded via load_top_repos(). Every stage shares this one scope, and it includes archived repos by default: an archived repo surfaces here as active=False rather than dropping out before the stage runs. This class-A set is also called the corevalue.md defines it.

The four checks

Four independent checks, each one boolean, each built from its own signals.

Check True when Builder
oss the repo carries an open-source license build_licenses
intent the repo shows any intent to be funded build_funding
nonprofit no company is strongly affiliated with the project build_funding
active NOT eol AND NOT archived build_active
graph LR
    osi["OSI ∪ FSF-libre<br/>∪ curated extras"]
    reg["registry · host API<br/>license strings"]
    gh["GitHub / GitLab<br/>Sponsors · FUNDING.yml"]
    ff["funding.json · npm/PyPI<br/>funding fields · PayPal"]
    oc["Open Collective"]
    ros["foundation rosters<br/>gitlab-hosts · overrides"]
    st["eol verdict<br/>archived flag"]

    reg --> lic["license (SPDX)<br/>4-source precedence"]
    lic --> oss["oss"]
    osi --> oss

    gh --> intent["intent<br/>OR of any signal"]
    ff --> intent
    oc --> intent
    ros --> host["institutional<br/>host / owner"]
    host --> intent
    host --> nonprofit["nonprofit<br/>False iff company"]

    st --> active["active<br/>NOT eol AND NOT archived"]

    oss --> elig["eligible<br/>plain AND"]
    intent --> elig
    nonprofit --> elig
    active --> elig
Loading

One source feeds two checks: an institutional host or owner both declares funding intent and decides the nonprofit exclusion. Nothing else is shared, and the four checks never trade off — each one alone can make a repo ineligible.

oss — an open-source license

build_licenses resolves one SPDX string per repo, then classifies it against the OSS-approved set. Four sources compete, in strict priority order:

# Source Where it comes from
1 manual assertion data/eligibility/overrides.csv license — fixes a repo whose LICENSE detection fails upstream
2 registry license (primary) each ecosystem's results.csv license column (npm / PyPI / crates / cpp — cpp joins Homebrew's, so Homebrew is not read directly); most common value across the repo's packages, ties alphabetical. Each fetcher also writes a durable raw/licenses.csv cache, which build_licenses falls back to when a value rebuild has stripped the license column
3 GitHub license (fallback) data/sources/github/repos.csv (GitHub Licensee detection)
4 GitLab license (fallback) data/sources/gitlab/repos.csv, keyed by the gl/ repo_id

A source claims the slot only with a meaningful license: an unknown sentinel (noassertion / other / none) never shadows a real detection from a later source. The approved set is data/sources/osi/oss-licenses.csv — OSI-approved ∪ FSF-libre software licenses ∪ curated extras, derived from the SPDX License List and documented in sources/osi.md. An SPDX expression (mit or apache-2.0, mit/apache-2.0, apache-2.0 with llvm-exception, gpl-3.0-or-later) counts as OSS when ANY component is approved.

Unknown stays distinguishable. In licenses.csv the flag is ternary — True / False / empty (no license signal at all) — but the rollup treats only oss=True as eligible, so unknown counts as not-OSS there.

intent — the project wants to be funded

OSE supports only those who want to be supported. intent is an OR, not a precedence chain — any one signal sets it True:

Signal Where it comes from
the owner's GitHub Sponsors listing is enabled any inbound count implies it
a declared channel a FUNDING.yml link or the file's mere presence, a repo- or org-level funding.json, an npm/PyPI funding field, a real Open Collective, a curated PayPal.me handle — GitHub and GitLab alike
a fundable bus factor maintainer a personal Sponsors listing ∪ the curated maintainer-overrides.csv
an institutional host or owner the scraped foundation rosters, gitlab-hosts.csv, overrides.csv

Outbound sponsoring is never intent: funding others is no channel for this repo. Intent propagates at the owner level — once any repo an owner has in scope self-declares a channel, every repo of that owner gets intent=True. That mirrors GitHub's own org-.github/personal-Sponsors semantics. The methodology, the score formula, the worked examples and the limits of what the fetchers can see are in components/funding.md.

nonprofit — no company is strongly affiliated

OSE supports only nonprofit initiatives. nonprofit is an exclusion rule, not proof of nonprofit status. It defaults True and flips False only when a curated or scraped company host or owner backs the repo (_nonprofit_flag in src/eligibility/build_funding.py). A True means "no corporate backer found", never "verified nonprofit". Company-backed repos are already resourced, so they are ineligible but stay visible in the table.

active — not EOL, not archived

OSE supports only projects that still have work ahead of them. active = NOT eol AND NOT archived. build_active combines two signals:

Signal Source Meaning
eol data/eligibility/overrides.csv eol the manual end-of-life verdict — True = project declared end-of-life; an explicit False pins a repo alive; empty = no verdict → alive
archived data/sources/github/repos.csv · data/sources/gitlab/repos.csv the host's archived flag, joined onto the scope by load_top_repos — GitHub rows from the Repos API, gl/ rows from the GitLab project API

The per-ecosystem registry EOL signals (data/sources/<eco>/eol.csv, from src.sources.<eco>.check_eol) flag packages — deprecations, yanks, endoflife.date dates — so they only inform the per-repo manual call.

Archival has no exemption: an archived repo is inactive, full stop. A project whose canonical upstream lives off GitHub is repointed at that upstream in data/value/overrides.csv, so it enters the pipeline as the live upstream rather than as the archived mirror — see value.md.

How the checks combine

src.eligibility.build_eligibility joins the four flags into data/eligibility/eligibility.csv. The rollup is a plain AND of four booleans, no weights:

eligible = oss AND intent AND nonprofit AND active

Signal completeness — not part of the verdict

The same builder stamps three signal-completeness columns. They report coverage; they never change the verdict.

Column Counts
value_comps how many of openssf_crit, eco_crit and top_eco_pct carry a real (non-empty, non-zero) value. pr_score, the fourth value_score component, is not counted
risk_comps the same count over the risk components (concentration, complexity, security, workload)
complete value_comps ≥ 2 AND risk_comps = 4 — a repo with full coverage

Archived repos and empty-tree stubs carry real risk scores, so they can be complete.

Stage overrides — data/eligibility/overrides.csv

One curated row per repo — or per org, when repo is an owner/* glob. An org row applies to every in-scope repo under that owner with no per-repo row; a per-repo row always wins. Three builders share the file:

Column Used by Meaning
repo all lowercased owner/name key (or owner/* org glob)
repo_id all host-namespaced repo id — gh/<numeric> or gl/<nickname>-<id>, the actual join key (rename-proof)
host, host_type build_funding legally-stewarding foundation or company, as a domain (rustfoundation.org), plus company/nonprofit. Scraped rosters instead emit a roster code (apache, fsf/gnu) — see components/funding.md
gh_user GitHub login (informational; no builder reads it)
owner, owner_type build_funding entity owning the GitHub org
oc_slug build_funding curated Open Collective slug; empty on a curated row = authoritative "no OC"
license build_licenses manual SPDX assertion (highest priority) when upstream detection fails — GitHub Licensee returns noassertion on bundled/dual/stacked LICENSE files, or the repo ships no standard LICENSE (e.g. node=mit, cpython=python-2.0, linux=gpl-2.0-only, icu=unicode-3.0)
paypal build_funding curated PayPal.me URL — a declared (unmeasured) funding channel
eol build_active manual end-of-life verdict (True/False/empty)
reason free-text audit context for the override

Repo-identity and validity corrections stay in data/value/overrides.csv. Two more curated files sit beside this one — maintainer-overrides.csv (login,reason) and gitlab-hosts.csv (gitlab_host,host,host_type,reason). Both feed build_funding; see components/funding.md.

Running it

scripts/run-pipeline.sh is the only supported entry point:

scripts/run-pipeline.sh --stage eligibility        # this stage alone
scripts/run-pipeline.sh --from-stage eligibility   # eligibility → preview → health

Per-step flags pass through to the stage runner (src.eligibility.run_eligibility_pipeline), so --list, --from <step>, --only <step>, --refresh composes with stage selection.

Every fetcher is incremental / TTL-gated. The stage fetches repo state (the GitHub repo-owner and GitLab project refreshes — the archived flag plus both license fallbacks), the license sets (the SPDX list, the derived OSS-approved set, the per-eco license fetchers), the per-eco EOL signals, and the whole funding group. bf-contributors runs serially ahead of that group, because maintainer-sponsors and funding-build both read it; the group's own step order is in components/funding.md.

Builders then run in order: licensesactivefunding-buildaggregate. The next stage's runner, src.preview.run_preview_pipeline, rebuilds every data/preview/ deliverable; scripts/pipeline_health.py (the health stage) then verifies each stage CSV matches its builder's current output, and scripts/stats.py recomputes the preview pipeline sheet.

What the model hands over

The pipeline ends at a ranked shortlist, never at a funding decision. src.preview.build_results writes data/preview/repos.csv with one row per top repo. Three columns turn that table into a queue:

Column Meaning
score sqrt(value_score × risk_score), the geometric mean of the value and risk scores, on the same absolute 0–100 scale as its inputs. It is computed for every row carrying both inputs, eligible or not.
priority a sequential rank (1, 2, 3 …) by score descending over eligible scored rows only. It is blank for an ineligible row and for any row with no score. The rank runs on the full-precision product, not on the displayed 2-decimal score, so near-ties keep a stable order.
eco_priority the same rank restarted within each ecosystem, so npm rank 1 and PyPI rank 1 both exist. Same rows and same order as priority — only the grouping differs, so a row blank in one is blank in the other.

priority is the grant-selection queue. Everything after it is manual and outside the model: check each project's eligibility by hand, contact the projects, then put the shortlist to the OSE board for final approval. The model contacts nobody, approves nothing and moves no money.

Read eligible as provisional. It is the model's verdict from the signals it can see, and manual due diligence confirms or overturns it. The README calls the queue "potentially eligible" for that reason.