From d63f62dd15380752d1a7f9b14ec733d3a409f1b8 Mon Sep 17 00:00:00 2001 From: arpan Date: Sat, 12 Sep 2026 04:45:24 +0530 Subject: [PATCH 1/2] Every commit certifies the Developer Certificate of Origin The repository had no contribution agreement at all: nothing said what a contributor asserts about the provenance of a change, which is the first thing a diligence review asks for and the one thing a project cannot add retroactively. The DCO is the answer that costs a contributor nothing and signals nothing about relicensing: the code stays Apache-2.0, copyright stays with its author, and the only artefact is a Signed-off-by trailer git adds on its own. The DCO 1.1 text is in the repository verbatim. CONTRIBUTING.md gets a "Sign your work" section that says what the trailer certifies and how to add it, and its commit-message rule now names Signed-off-by as the one trailer that belongs. The pull request template gets a line for it. CI enforces it with a shell loop in a new `dco` job rather than the DCO app, so the rule is pinned in the repository like everything else and there is nothing to install or quietly uninstall. It runs on pull requests only, reads every non-merge commit in the range, requires a Signed-off-by trailer carrying the author's email, and exempts bot authors (dependabot signs anyway). The repository-signals test asserts the job's gate, its exemption, and that both documents say what they now must. Signed-off-by: arpan --- .github/PULL_REQUEST_TEMPLATE.md | 1 + .github/workflows/ci.yml | 30 ++++++++++++++++++++++++++++ CONTRIBUTING.md | 20 ++++++++++++++++++- DCO | 34 ++++++++++++++++++++++++++++++++ tests/test_repository_signals.py | 16 +++++++++++++++ 5 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 DCO diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index f848981..ae29b80 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -14,6 +14,7 @@ - [ ] **`scripts/check.sh` green** under the project's interpreter. - [ ] **Independent review** requested for anything touching authorization, identity, delegation, the gateway, an adapter or the store. - [ ] **Nothing in `src/` merges on green CI alone**; a maintainer reads it. +- [ ] **Signed off.** Every commit carries `Signed-off-by` with its author's email (`git commit -s`); CONTRIBUTING.md's *Sign your work* says what that certifies. ## Mutation table diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de477a9..171851b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -408,3 +408,33 @@ jobs: timeout 60 /tmp/qs/bin/ctrlrun demo /tmp/qs/bin/ctrlrun init test -f ctrlrun.yaml + + dco: + # Every commit on a pull request carries a `Signed-off-by:` trailer naming its author's + # email: the Developer Certificate of Origin, per CONTRIBUTING.md. A shell loop rather than + # the DCO app, so the rule lives in the repository, pinned like everything else, with + # nothing to install and nothing that can be quietly uninstalled. Bot commits are exempt; + # dependabot signs anyway. + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + + - name: Every commit is signed off by its author + env: + BASE: ${{ github.event.pull_request.base.sha }} + HEAD: ${{ github.event.pull_request.head.sha }} + run: | + missing=0 + for sha in $(git rev-list --no-merges "$BASE..$HEAD"); do + name="$(git log -1 --format=%an "$sha")" + case "$name" in *'[bot]') continue ;; esac + email="$(git log -1 --format=%ae "$sha")" + if ! git log -1 --format=%B "$sha" | grep -F 'Signed-off-by: ' | grep -Fq "<$email>"; then + echo "::error::$sha ($(git log -1 --format=%s "$sha")) has no 'Signed-off-by: $name <$email>' trailer. Add one with 'git commit --amend -s', or to every commit on the branch with 'git rebase --signoff $BASE'." + missing=1 + fi + done + [ "$missing" -eq 0 ] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 00e6354..f132c9a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -152,7 +152,25 @@ opening the pull request. A pull request touching only tooling, docs or CI merges on green. - Merge stacked pull requests bottom-up, and never delete a branch another open pull request targets. -- Commit messages say what changed and why, in prose. No attribution trailers. +- Commit messages say what changed and why, in prose, and end with the `Signed-off-by` + trailer described below. No attribution trailers. + +## Sign your work + +Every commit carries a `Signed-off-by` trailer, and the trailer certifies the +[Developer Certificate of Origin](DCO): that you wrote the change or have the right to submit +it under the licence in [LICENSE](LICENSE), and that you know the contribution and its record +are public. That is the whole agreement. There is no contributor licence agreement and no +copyright assignment: your copyright stays yours, and what you contribute goes out under +Apache-2.0 like the rest of the code. + +`git commit -s` adds the trailer from your git identity: + + Signed-off-by: Your Name + +The email has to be the commit's author email. CI's `dco` job reads every commit on a pull +request and names any that lack the trailer; `git rebase --signoff main` adds it to a whole +branch at once. Commits from bots are exempt. ## Releases diff --git a/DCO b/DCO new file mode 100644 index 0000000..49b8cb0 --- /dev/null +++ b/DCO @@ -0,0 +1,34 @@ +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/tests/test_repository_signals.py b/tests/test_repository_signals.py index 8cb174f..a8f9714 100644 --- a/tests/test_repository_signals.py +++ b/tests/test_repository_signals.py @@ -181,6 +181,9 @@ def test_the_community_files_exist_and_say_what_they_must(): "CTRLRun/ctrlrun-docs", "tools/docs_audit", "trusted publishing", + # The contribution agreement is the DCO and nothing more; the file has to say so. + "Developer Certificate of Origin", + "git commit -s", ): assert phrase in contributing, phrase @@ -215,6 +218,7 @@ def test_the_community_files_exist_and_say_what_they_must(): "Mutation table", "CLAIMS.md", "docs_audit", + "Signed-off-by", ): assert phrase in pr_template, phrase @@ -223,6 +227,18 @@ def test_the_community_files_exist_and_say_what_they_must(): ) +def test_every_pull_request_commit_is_signed_off(): + """CONTRIBUTING.md asks for a `Signed-off-by` trailer on every commit; the `dco` job is + what makes that a gate rather than a request. It runs on pull requests only: a push to + `main` is a merge of commits the job already read.""" + job = _workflow("ci.yml")["jobs"]["dco"] + assert job["if"] == "github.event_name == 'pull_request'" + run = "\n".join(step.get("run", "") for step in job["steps"]) + assert "Signed-off-by: " in run + assert "--no-merges" in run + assert "[bot]" in run, "bot commits are exempt, and the exemption has to be visible" + + def test_the_citation_names_the_repository_the_version_and_the_tagline(): citation = yaml.safe_load((REPO_ROOT / "CITATION.cff").read_text(encoding="utf-8")) with (REPO_ROOT / "pyproject.toml").open("rb") as handle: From ef3d94fa84aa8641f92ebf33e556200b8c968744 Mon Sep 17 00:00:00 2001 From: arpan Date: Sat, 12 Sep 2026 05:43:59 +0530 Subject: [PATCH 2/2] The sign-off check reads git's trailer block, and exempts nobody Two review findings against the first version of the `dco` job. A substring grep over the whole commit message accepted any line that contained `Signed-off-by: ` and the author's email, so a sentence in the body could satisfy it without a trailer ever being added. The check now asks git for the parsed trailer block alone, with `only` and `valueonly`, and requires a value ending in the author's email, angle brackets included, so a prefix of the email does not pass either. The exemption for authors whose name ended in `[bot]` was a string anyone can set with `git config user.name`. It is gone, and so is the idea of an exemption: dependabot signs its commits off already, and any other bot that opens a pull request here can do the same. CONTRIBUTING.md and the repository-signals test say both things. Signed-off-by: arpan --- .github/workflows/ci.yml | 13 +++++++++---- CONTRIBUTING.md | 3 ++- tests/test_repository_signals.py | 7 +++++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 171851b..e649b61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -413,8 +413,10 @@ jobs: # Every commit on a pull request carries a `Signed-off-by:` trailer naming its author's # email: the Developer Certificate of Origin, per CONTRIBUTING.md. A shell loop rather than # the DCO app, so the rule lives in the repository, pinned like everything else, with - # nothing to install and nothing that can be quietly uninstalled. Bot commits are exempt; - # dependabot signs anyway. + # nothing to install and nothing that can be quietly uninstalled. Only git's own trailer + # block counts, so a sentence in the body that happens to mention the trailer does not. + # Nobody is exempt, bots included: an exemption keyed on a name or an email is a string + # anyone can set, and dependabot signs its commits off anyway. if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: @@ -430,9 +432,12 @@ jobs: missing=0 for sha in $(git rev-list --no-merges "$BASE..$HEAD"); do name="$(git log -1 --format=%an "$sha")" - case "$name" in *'[bot]') continue ;; esac email="$(git log -1 --format=%ae "$sha")" - if ! git log -1 --format=%B "$sha" | grep -F 'Signed-off-by: ' | grep -Fq "<$email>"; then + # `only` restricts the output to the trailer block git itself recognises, and + # `valueonly` leaves the `Name ` part; the value has to end in the author's + # email, angle brackets included. + if ! git log -1 --format='%(trailers:key=Signed-off-by,valueonly,only)' "$sha" \ + | awk -v want="<$email>" 'substr($0, length($0) - length(want) + 1) == want { found = 1 } END { exit !found }'; then echo "::error::$sha ($(git log -1 --format=%s "$sha")) has no 'Signed-off-by: $name <$email>' trailer. Add one with 'git commit --amend -s', or to every commit on the branch with 'git rebase --signoff $BASE'." missing=1 fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f132c9a..825a9c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -170,7 +170,8 @@ Apache-2.0 like the rest of the code. The email has to be the commit's author email. CI's `dco` job reads every commit on a pull request and names any that lack the trailer; `git rebase --signoff main` adds it to a whole -branch at once. Commits from bots are exempt. +branch at once. Only git's own trailer block counts, and nobody is exempt, bots included: +dependabot signs its commits off already. ## Releases diff --git a/tests/test_repository_signals.py b/tests/test_repository_signals.py index a8f9714..3a8701c 100644 --- a/tests/test_repository_signals.py +++ b/tests/test_repository_signals.py @@ -234,9 +234,12 @@ def test_every_pull_request_commit_is_signed_off(): job = _workflow("ci.yml")["jobs"]["dco"] assert job["if"] == "github.event_name == 'pull_request'" run = "\n".join(step.get("run", "") for step in job["steps"]) - assert "Signed-off-by: " in run assert "--no-merges" in run - assert "[bot]" in run, "bot commits are exempt, and the exemption has to be visible" + # Only git's parsed trailer block counts, so a sentence in the body that mentions the + # trailer cannot satisfy the check; and there is no exemption, because one keyed on a + # name or an email is a string anyone can set. + assert "trailers:key=Signed-off-by" in run + assert "[bot]" not in run def test_the_citation_names_the_repository_the_version_and_the_tagline():