Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

## Related issue

<!-- Link an issue if there is one, for example: Fixes #123. -->
<!-- Link the approved issue (labeled ready-for-contribution), e.g. Fixes #123.
Maintainers, vouched contributors, and approved bots can skip this.
Details: CONTRIBUTING.md#contribution-eligibility -->

## How did you check it?

Expand Down
26 changes: 26 additions & 0 deletions .github/contribution-policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"vouchedContributors": [
{
"id": 44305048,
"login": "andrejsshell",
"reason": "Core team (usekaneo/developers)."
},
{
"id": 127273550,
"login": "randoneering",
"reason": "Core team (usekaneo/developers)."
},
{
"id": 176929823,
"login": "tinsever",
"reason": "Core team (usekaneo/developers)."
}
],
"exemptBots": [
{
"id": 49699333,
"login": "dependabot[bot]",
"reason": "Repository-managed dependency updates."
}
]
}
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ jobs:
- name: Test release security controls
run: |
docker pull nginx:1.29.5-alpine
node --test scripts/security/*.test.mjs scripts/ci/*.test.mjs
node --test scripts/security/*.test.mjs scripts/ci/*.test.mjs scripts/contribution-eligibility/*.test.mjs

- name: Run unit tests
env:
Expand Down
64 changes: 64 additions & 0 deletions .github/workflows/contribution-eligibility.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Contribution eligibility

on:
# zizmor: ignore[dangerous-triggers] Only executes policy code from main;
# never checks out or executes pull-request content.
pull_request_target:
types: [opened, edited, synchronize, reopened, ready_for_review]
Comment thread
tinsever marked this conversation as resolved.
Outdated
issues:
types: [labeled, unlabeled, closed, reopened, deleted, transferred]
label:
types: [edited, deleted]
# zizmor: ignore[dangerous-triggers] Reads GitHub metadata and trusted main
# code only; does not consume artifacts or code from the triggering run.
workflow_run:
workflows: [CI]
types: [completed]
push:
branches: [main]
paths:
- .github/contribution-policy.json
- .github/workflows/contribution-eligibility.yml
- scripts/contribution-eligibility/**
# Manual sidebar links and permission changes have no matching Actions event.
schedule:
- cron: '17 * * * *'
workflow_dispatch:

permissions:
contents: read
pull-requests: read
issues: read
checks: write

concurrency:
group: contribution-eligibility
cancel-in-progress: false

jobs:
reconcile:
# Dependabot-triggered runs may receive a read-only token. CI completion
# rechecks its PR using a token issued in the trusted workflow context.
if: >-
(github.event_name != 'pull_request_target' || github.actor != 'dependabot[bot]') &&
(github.event_name != 'workflow_run' || github.event.workflow_run.actor.login == 'dependabot[bot]')
Comment thread
tinsever marked this conversation as resolved.
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 15
steps:
- name: Check out current trusted policy
uses: useblacksmith/checkout@25227e61ff9dafe400e22fa487b673eac4e4409a # v1
with:
# Always read the latest policy, including when an older run is queued.
ref: main
persist-credentials: false

- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24.19.0
package-manager-cache: false

- name: Check open pull requests
env:
GH_TOKEN: ${{ github.token }}
run: node scripts/contribution-eligibility/check.mjs
80 changes: 76 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Thanks for wanting to contribute to Kaneo! Whether you're fixing bugs, adding fe
- [Getting Started](#getting-started)
- [Making Your First Contribution](#making-your-first-contribution)
- [Finding Something to Work On](#finding-something-to-work-on)
- [Contribution Eligibility](#contribution-eligibility)
- [The Process](#the-process)
- [Development Guidelines](#development-guidelines)
- [Code Style](#code-style)
Expand Down Expand Up @@ -36,9 +37,79 @@ Follow the [local development setup guide](ENVIRONMENT_SETUP.md) for prerequisit

### Finding Something to Work On

- **Browse [open issues](https://github.com/usekaneo/kaneo/issues)** - look for "good first issue" labels
- **Browse [issues ready for contribution](https://github.com/usekaneo/kaneo/issues?q=is%3Aissue%20is%3Aopen%20label%3Aready-for-contribution)** - the "good first issue" ones are a nice place to start
- **Check our [Discord](https://discord.gg/rU4tSyhXXU)** - we often discuss features and bugs there
- **Found a bug?** Feel free to fix it and open a PR
- **Found a bug?** Open an issue first and wait for a maintainer to give the go-ahead before sending a PR (unless you're [exempt](#contribution-eligibility))

### Contribution Eligibility

Please talk to us before you start writing code. Find or open an issue and
agree on the scope with a maintainer. Unless someone has vouched for you, your
PR needs to link an open issue in this repo with the **`ready-for-contribution`**
label. Opening an issue yourself isn't approval; a maintainer adds the label
once the work is agreed.

Link the issue in your PR description with `Fixes #123` or `Closes #123`.
GitHub only picks those up for PRs against the default branch, so for other
branches a maintainer can link the issue from the PR's Development sidebar.
Just mentioning it (`See #123`) isn't enough.

You can skip the issue if you're a repo admin, have the **maintain** role, have
been vouched for, or are an approved bot. A voucher is tied to one GitHub
account, can't be handed to someone else, and only skips the issue step.
Everyone still goes through code review, CI, and the [AI policy](AI_POLICY.md).
Past contributions or org membership don't earn a voucher automatically.

The **Contribution eligibility** check on your PR tells you where you stand. If
it fails, link an approved issue or ask a maintainer to vouch for you.

The check reruns when you edit the PR description or push commits, when an
issue gains or loses the label, and when the voucher list changes. It can't
react to sidebar links or permission changes, so an hourly scheduled run picks
those up (GitHub sometimes runs these late). Maintainers can also trigger the
workflow by hand. Dependabot PRs are checked after their CI run finishes,
because bot-triggered workflows may only get read-only tokens.

Once the check is required, a failing result blocks merging. It won't stop
anyone from opening a PR, and it never closes one.

#### Managing Vouchers

Vouchers live in
[`.github/contribution-policy.json`](.github/contribution-policy.json). To vouch
for someone, add them to `vouchedContributors` in a PR to `main`. To revoke,
remove them. Review these changes the same way you'd review code.

```json
{
"id": 123456789,
"login": "example-contributor",
"reason": "Consistently submits focused changes and follows through on review."
}
```

Get the numeric ID with `gh api users/USERNAME --jq '{id,login}'`. Only the ID
matters for eligibility; `login` and `reason` are there so people can tell who
it is and why. That way a renamed account keeps its voucher, and whoever grabs
an old username doesn't inherit it.

Bots go in `exemptBots`. For now that's just Dependabot.

To turn on enforcement once this workflow is on `main`:

1. Create the `ready-for-contribution` label and only put it on work you've agreed to.
2. Run the **Contribution eligibility** workflow once so it checks PRs that are already open.
3. In the branch rules for `main`, require the **Contribution eligibility**
status check with GitHub Actions as the source. Do the same for any other
protected branches people contribute to. Don't pick the workflow's
`reconcile` job: the script publishes its own check on each PR's head
commit, and that's the one to require.

The automation only reads code and policy from `main`, relies on GitHub's own
issue links, and never runs anything from the PR. If the policy file is broken
or the GitHub API errors out, the check fails instead of letting the PR
Comment thread
tinsever marked this conversation as resolved.
Outdated
through. Revoking a voucher, removing the label, or closing the issue all
trigger a recheck of open PRs.

### The Process

Expand All @@ -63,7 +134,8 @@ git commit -m "docs: update deployment guide"
git push origin your-branch-name
```

Then open a pull request on GitHub with a clear description of what you changed and why.
Then open a pull request on GitHub that explains what you changed and why. Link
the approved issue too, unless you're exempt.

## Development Guidelines

Expand Down Expand Up @@ -207,7 +279,7 @@ Please read and follow our [AI Contribution Policy](AI_POLICY.md), which adopts

## Types of Contributions We Love

- **Bug fixes** - Found something broken? Fix it!
- **Bug fixes** - Found something broken? Open an issue so we can agree on the fix
- **New features** - Have an idea? Let's discuss it first
- **Documentation** - Help others understand how to use Kaneo
- **Performance improvements** - Make things faster
Expand Down
9 changes: 9 additions & 0 deletions scripts/ci/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ upgrade-rendering regression checks. Configure the new job checks as required in
GitHub if they should block merging; editing workflows alone does not change
branch protection.

**Contribution eligibility** is a separate workflow. It posts a status on the
head commit of every open PR. Maintainers, vouched accounts, and approved bots
pass without an issue; everyone else has to link an open
`ready-for-contribution` issue from this repo. The workflow always takes its
code and policy from `main`, never from the PR. Voucher management and the
branch-rule setup are covered in
[the contribution guide](../../CONTRIBUTING.md#contribution-eligibility). Run
its tests with `node --test scripts/contribution-eligibility/*.test.mjs`.

## Local runtime checks

Use disposable services only. The HTTP helpers refuse non-loopback origins.
Expand Down
31 changes: 31 additions & 0 deletions scripts/contribution-eligibility/check-runs.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { statusContext } from "./policy.mjs";

export function checkPublisher(github) {
const ids = new Map();
return async (sha, result) => {
const body = {
name: statusContext,
status: result.state === "pending" ? "in_progress" : "completed",
...(result.state === "pending"
? {}
: { conclusion: result.state === "success" ? "success" : "failure" }),
details_url: `https://github.com/${github.repository}/blob/main/CONTRIBUTING.md#contribution-eligibility`,
output: {
title: result.description,
summary: `${result.description}\n\n[Contribution rules](https://github.com/${github.repository}/blob/main/CONTRIBUTING.md#contribution-eligibility).`,
},
};
const id = ids.get(sha);
const check = await github.request(
`repos/${github.repository}/check-runs${id ? `/${id}` : ""}`,
{
method: id ? "PATCH" : "POST",
body: id ? body : { ...body, head_sha: sha },
},
);
if (!Number.isSafeInteger(check.id) || check.id <= 0) {
throw new Error("GitHub did not return a check run ID.");
}
ids.set(sha, check.id);
};
}
Loading