CI automation and contribution tiers#
Reference for how pull requests are classified, which CI stages run, and which branch rules apply. For what to do once your PR is open, see Review and integration.
Verify every change, automatically#
Every contribution is risk-scored before any human reviews it. The classifier reads its policy from the base branch so the PR cannot rewrite its own judge.
10
Parallel baseline checks per PR.
REUSE · DCO · Bandit · pre-commit · build · tests · docs · ...
3
Automated tier levels.
Tier 0 · Tier 1 · Tier 2, computed from the diff.
1
Single source of truth.
tier-policy.yml, read from the base branch, never from the PR.
100 %
Auto-classified changes.
No labels to set. The CI scores every PR before any human reviews it.
Contribution tiers in one paragraph#
Every pull request is automatically classified into one of three tiers based on the diff it introduces. The tier determines which CI stages run and how many approvals are required to merge.
Tiers 0, 1 and 2 are computed automatically by the CI from the policy declared in
.github/tier-policy.yml. The contributor does not assign the tier; the CI does.Tier 3 is a separate qualitative governance category for release decisions and policy changes that go through an ESA process outside the automated pipeline.
The three automated tiers#
Tier 0 · Baseline
No risk-elevating change detected.
Triggers
No
locked_pathsmodified.No SME-owned paths touched.
Baseline marker unchanged.
Approvals: 1 maintainer. CI: Baseline gate only.
Examples: bug fixes, doc updates, refactors without scientific impact, typo corrections.
Tier 1 · Extended
Locked or SME-owned paths modified.
Triggers
Touches
locked_paths.Touches SME-owned paths.
Baseline marker differs.
Dependabot major version bump.
Approvals: 2 (incl. SME). CI: Baseline + Extended.
Examples: small parameter updates, minor algorithm changes, CI configuration changes.
Tier 2 · Heavy
Release branch or explicit upclass.
Triggers
PR targets the
releasebranch.run_heavy=truerequested on dispatch.Manual upclass from Tier 1.
Approvals: 3 (incl. SME and ESA). CI: Baseline + Extended + Heavy.
Examples: new retrieval approaches, significant changes to error
models, anything promoted to release.
Tier 3 · governance#
Tier 3 is a separate path for release decisions and policy changes. It is not computed by the CI. These changes are approved through an explicit ESA decision in consultation with the governance group, then reflected in the repository through the standard branching mechanism once the decision has been recorded.
Examples: new processor version releases, governance changes, major policy updates.
Judge from base, never from PR#
Important
The tier policy is read from the base branch SHA, never from the PR head. The policy file, the baseline references, and the test harness are all locked at the base commit. A pull request cannot rewrite its own judge.
This is the core security guarantee of the CI: every PR is evaluated against the rules in effect on the branch it targets, not against the rules it tries to introduce.
Tier comparison#
Tier 0 · Baseline |
Tier 1 · Extended |
Tier 2 · Heavy |
|
|---|---|---|---|
CI stages |
Baseline |
Baseline + Extended |
Baseline + Extended + Heavy |
Approvals |
1 maintainer |
2 (incl. SME) |
3 (incl. SME + ESA) |
Expected volume |
60–70% of PRs |
20–25% of PRs |
5–10% of PRs |
Typical timeline |
under 3 days |
5 to 7 days |
10 to 14 days |
Review cadence |
Weekly |
Monthly |
Quarterly |
Pipeline overview#
When a pull request is opened, GitHub triggers ci.yml. The workflow runs
baseline checks in parallel, classifies the tier from the base-branch policy,
then runs Extended and Heavy stages when required.
Diagram appendix#
Pull request sequence#
%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#f5f5f5','primaryTextColor':'#333','primaryBorderColor':'#9e9e9e','lineColor':'#666','secondaryColor':'#f5f5f5','tertiaryColor':'#f5f5f5'}}}%%
sequenceDiagram
participant Dev as Developer
participant Fork as Fork (external only)
participant GH as GitHub PR
participant CI as CI/CD (ci.yml)
participant Bot as PR Guidance Bot
participant Rev as Reviewer(s)
participant Develop as develop
alt Internal contributor
Dev->>GH: Push feature branch · open PR → develop
else External contributor
Dev->>Fork: Push feature branch on fork
Fork->>GH: Open PR from fork → upstream develop
end
GH->>CI: pull_request event triggers ci.yml
CI->>CI: Baseline checks in parallel<br/>(DCO · REUSE · pre-commit · bandit · build · unit tests · docs)
alt Baseline failure
CI->>GH: baseline-gate ✗
GH->>Dev: Fix failing job and re-push
Dev->>GH: Push fix (or to fork)
GH->>CI: Re-trigger ci.yml
end
CI->>CI: Automatic tier triage<br/>(read tier-policy.yml from base branch)
alt Tier 1: SME-owned paths modified
CI->>CI: Extended pipeline (pytest -m extended)
end
alt Tier 2: locked paths modified
CI->>CI: Extended + Heavy pipeline (pytest -m heavy)
end
CI->>GH: CI gate ✓
CI->>Bot: Post/update sticky PR comment
Bot->>GH: Summary: tier · jobs status · reviewers needed
Rev->>GH: Review and comments
Dev->>GH: Address comments · re-push
GH->>CI: Re-trigger ci.yml
Rev->>GH: Approve (N approvals per branch ruleset)
GH->>Develop: Squash merge
Branch protection and approvals#
Four branches, three protection levels, one promotion path. Every branch is governed by GitHub repository rulesets, never by ad-hoc settings.
feature/*Personal
Free editing. No approval required. This is where you build the change before it touches the project.
developIntegration
1 approval · Maintainer + SME review · squash only ·
CI gate required.
releasePre-release
2 approvals · Maintainer + SME · GPG/SSH signed commits · squash only · Heavy CI mandatory.
mainProduction
3 approvals · Maintainer + SME + ESA · no admin bypass · signed commits · squash only.
Rules enforced everywhere#
✓ Require CODEOWNERS review.
✓ Require a linear history (squash-only merges).
✓ Dismiss stale approvals on every new commit.
✓ Block force-pushes.
✓
CI gateis the required status check on every protected branch.
Important
PRs targeting release need explicit Heavy authorisation. The
CI gate stays red on every pull_request event until a maintainer
triggers the workflow via Actions → Run workflow with
run_heavy=true. This is the explicit consent step before promotion
to main.
Any modification to VERSION or CHANGELOG.md requires an explicit
approval from the ESA reviewer defined in CODEOWNERS. No release
can be merged without this approval.
Branching strategy#
main: Operational/production branch. Promoted to fromreleaseafter ESA approval.release: Release candidate branch. Pre-release validation runs here; Heavy CI is mandatory before promotion tomain.develop: Main development branch. Latest development state.Feature branches:
feature/description,bugfix/issue-id,docs/topic. Opened fromdevelopon a fork.
Dependabot: automated dependency updates#
Dependabot opens PRs every Monday to update pip and GitHub Actions dependencies, always targeting develop. These PRs go through the normal CI pipeline.
Important rule: a major version bump on a pip dependency is automatically classified as Tier 1 by the CI, requiring SME review.
Automated CI/CD checks#
All pull requests go through automated checks. The required checks and approvals depend on the contribution tier.
Baseline Pipeline (all PRs: 10 parallel jobs):
baseline-marker-signal:pytest -m baselineontest/baseline/(feeds the tier decision)baseline-dco: Signed-off-by trailer on every non-merge commit, identity must match author or committerbaseline-reuse: REUSE / SPDX header compliancebaseline-pre-commit: black, ruff, mypy, detect-secrets hooksbaseline-security: Bandit static analysisbaseline-build: sdist + wheel build (non-blocking during migration)baseline-sensitive-files: rejects*.bakfiles and files larger than 10 MBbaseline-unit-tests:pytest -m unit(non-blocking during migration)baseline-docs: Sphinx build ifdocs/api/conf.pyexists (non-blocking)baseline-dependabot: verifies.github/dependabot.ymlexists and signals major bumps
All 10 jobs feed baseline-gate, the aggregate blocking check.
Extended Pipeline (Tier 1+):
Extended test suite (
pytest -m extended)⚠ Requires TDS dataset: see Code standards for access procedure
Heavy Pipeline (Tier 2):
Full scientific regression (
pytest -m heavy)⚠ Requires TDS dataset: see Code standards for access procedure
Automatic tier detection#
The CI/CD pipeline automatically determines the tier by analysing the files modified in your PR against the policy defined in .github/tier-policy.yml. You do not need to add a label manually.
How the tier is decided:
Files listed in
locked_paths(e.g.VERSION,pyproject.toml,.github/workflows/**,CODEOWNERS, test directories) → Tier 2 automaticallyFiles in
sme_owned_paths(processor directoriesbps-*) → triggers SME reviewA Dependabot PR with a major version bump → Tier 1 automatically
Everything else → Tier 0
The policy file is always read from the base branch (not your PR head), which prevents a PR from modifying its own judge.
What runs based on the detected tier:
Tier 0: Baseline pipeline only (DCO, REUSE, pre-commit, security, build, unit tests, docs)
Tier 1: Baseline + Extended pipeline (
pytest -m extended)Tier 2: Baseline + Extended + Heavy pipeline (
pytest -m heavy)Tier 3: ESA governance decision: no automated pipeline
Review and approval requirements#
Tier 0: At least 1 core maintainer approval
Tier 1: Scientific module expert approval + Core maintainer approval
Tier 2: Scientific module expert(s) approval + Open Science Lead approval + ESA representative(s) approval + Core maintainer approval
Tier 3: Explicit ESA decision in consultation with governance group
The PR cannot be merged until all required checks pass and all required approvals are obtained.
Full policy rules: .github/tier-policy.yml.
Previous: Review and integration | Next: Quality and validation