Skip to content

ci: the DCO rewrite exemption keys on the committer, not the address shape - #81

Merged
vyncint merged 1 commit into
mainfrom
ci/dco-web-flow-committer
Sep 13, 2026
Merged

vyncint merged 1 commit into
mainfrom
ci/dco-web-flow-committer

Conversation

@vyncint

@vyncint vyncint commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Fixes the attribution and DCO over full history failure on main.

What is wrong

#44 widened the squash-rewrite exemption by asking whether either address is the <id>+<login>@users.noreply.github.com form. That shape is a symptom of a GitHub rewrite, not the rule.

An outside contributor's squash landed authored as <jackchen@haloailabs.com> while every commit they pushed was authored and signed off as <nightcityblade@gmail.com>. Two real addresses, neither a noreply form, so the exemption could not see it. Two commits on main fail the scan for this reason, and because the scan walks every reachable commit, no follow-up commit can clear them and every PR branched from main inherits the failure.

The fix

The signature of a GitHub-composed commit is the committer, which is always GitHub <noreply@github.com>. The exemption keys on that now. It is the rule oxidelake and termlens already use, so this aligns the three.

For a GitHub-composed commit the requirement is that a DCO assertion is present, not that it matches an address GitHub picked. Matching stays enforced where it means something: this job also runs on every pull request, over the contributor's own commits, which they authored and pushed — those take the exact-match branch. A squash cannot reach main without that having passed first.

Verification

The scan is green over all 36 commits of main. Every case below was run in isolation against a crafted commit, using the script extracted verbatim from this workflow:

case expected got
human commit signed by a different person FAIL FAIL
human commit, no trailer FAIL FAIL
web-flow commit, no trailer at all FAIL FAIL
one name, two unrelated addresses, no web-flow marker FAIL FAIL
bot author FAIL FAIL
AI co-author trailer FAIL FAIL
generated-with watermark FAIL FAIL
human commit, matching trailer PASS PASS
contributor account-address shape (780796dd) PASS PASS
maintainer-trailer-only shape (ca56368d) PASS PASS
owner flip that #44 was written for (e1ceae0) PASS PASS

Residual risk, stated

This does not catch a maintainer composing a custom squash body that drops the contributor's Signed-off-by:. That is how ca56368d lost mikemikimike's, which they did give on their own commit. The PR-time check is what stands behind the assertion, and CONTRIBUTING now tells maintainers to carry those trailers into a squash body.

…shape

#44 widened the exemption for GitHub's squash rewrite by asking whether
either address is the <id>+<login>@users.noreply.github.com form. That
shape is a symptom, not the rule. An outside contributor's squash landed
authored as <jackchen@haloailabs.com> while every commit they pushed was
authored and signed off as <nightcityblade@gmail.com>: two real addresses,
neither a noreply form, and the exemption could not see it. Two commits on
main fail the scan for this reason and no follow-up commit can clear them,
because the scan walks every reachable commit.

The signature of a GitHub-composed commit is the committer, which is
always GitHub <noreply@github.com>. That is what the exemption tests now,
which is the rule oxidelake and termlens already use.

For such a commit the requirement is that a DCO assertion is present, not
that it matches an address GitHub chose. Matching stays enforced where it
means something: this job runs on every pull request over the
contributor's own commits, which they authored and pushed, and those take
the exact-match branch. A squash cannot reach main without that having
passed.

Exercised against crafted commits, every case in isolation. Must fail, and
does: a human commit signed by a different person, a human commit with no
trailer, a web-flow commit with no trailer at all, one name with two
unrelated addresses and no web-flow marker, a bot author, an AI co-author
trailer, a generated-with watermark. Must pass, and does: a human commit
with a matching trailer, the contributor-account-address shape, the
maintainer-trailer-only shape, and the owner flip #44 was written for. The
scan is green over all 36 commits of main.

What it does not catch is a maintainer dropping a contributor's trailer
while composing a squash body, which is how one of those two commits lost
its. The PR-time check is what stands behind the assertion; CONTRIBUTING
now says to carry those trailers.

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
@vyncint
vyncint merged commit 1a7e064 into main Sep 13, 2026
14 checks passed
@vyncint
vyncint deleted the ci/dco-web-flow-committer branch September 13, 2026 16:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant