From 73787002ac91acc7594d92911ad272443a2d3c6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cpedrodneves=E2=80=9D?= <“pedro@canton.foundation”> Date: Fri, 12 Jun 2026 18:38:43 +0100 Subject: [PATCH 1/2] ci: add DCO sign-off enforcement workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: “pedrodneves” <“pedro@canton.foundation”> --- .github/workflows/dco.yml | 103 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 .github/workflows/dco.yml diff --git a/.github/workflows/dco.yml b/.github/workflows/dco.yml new file mode 100644 index 0000000..02c0398 --- /dev/null +++ b/.github/workflows/dco.yml @@ -0,0 +1,103 @@ +# ───────────────────────────────────────────────────────────────────────────── +# DCO (Developer Certificate of Origin) Enforcement Workflow +# +# Place this file at: .github/workflows/dco.yml +# in the canton-network/.github repo to enforce org-wide. +# +# What it does: +# - Runs on every pull request (opened, updated, or synchronized) +# - Checks that EVERY commit in the PR contains a "Signed-off-by:" trailer +# - Fails the required status check if any commit is missing the sign-off +# - Leaves a clear comment on the PR explaining how to fix it +# ───────────────────────────────────────────────────────────────────────────── + +name: DCO Check + +# The contributor-assistant action requires BOTH pull_request_target AND +# issue_comment triggers to function correctly: +# - pull_request_target → fires when a PR is opened/updated +# - issue_comment → fires when a contributor types 'recheck' +on: + pull_request_target: + types: [opened, synchronize, reopened] + issue_comment: + types: [created] + +jobs: + dco: + name: Require Signed-off-by on all commits + runs-on: ubuntu-latest + + # Permissions required by contributor-assistant/github-action + permissions: + actions: write # re-run checks + contents: write # read commit history + pull-requests: write # post help comments on the PR + statuses: write # set the commit status check (✅/❌) + + steps: + # ── Check DCO sign-off on every commit in the PR ───────────────────── + - name: Check DCO sign-off + # Only run when a PR is opened/updated, OR when a contributor + # comments 'recheck' to manually re-trigger the check. + if: > + github.event_name == 'pull_request_target' || + github.event.comment.body == 'recheck' + uses: contributor-assistant/github-action@v2.6.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # ── DCO mode ──────────────────────────────────────────────────── + # use-dco-flag switches the action from CLA click-through mode + # into DCO mode: every commit must have a Signed-off-by trailer. + use-dco-flag: true + + # path-to-signatures is required by the action even in DCO mode; + # point it at a harmless path — it won't be written to. + path-to-signatures: 'dco-signatures/signatures.json' + + # branch where the (unused) signature file would live + branch: 'main' + + # ── Allow bot commits to skip the sign-off check ───────────── + # Automated commits from Dependabot, Renovate, etc. cannot add + # a Signed-off-by trailer, so we exempt them here. + allowlist: bot,dependabot[bot],renovate[bot],github-actions[bot] + + # ── Custom PR comment when sign-off is missing ─────────────── + custom-notsigned-prcomment: | + ## ❌ DCO Sign-off Missing + + One or more commits in this pull request are missing a + `Signed-off-by` trailer. This is required by the + [Developer Certificate of Origin (DCO)](https://developercertificate.org/). + + ### How to fix it + + **Option A – amend the last commit (single-commit PR):** + ```bash + git commit --amend --signoff + git push --force-with-lease + ``` + + **Option B – rebase and sign off all commits at once:** + ```bash + git rebase HEAD~ --signoff + git push --force-with-lease + ``` + + **Option C – never forget again (global git config):** + ```bash + git config --global format.signoff true + ``` + + > The trailer should look like: + > `Signed-off-by: Your Name ` + > + > Once fixed, comment `recheck` on this PR to re-run the check. + + # ── Custom PR comment when all commits are signed off ──────── + custom-allsigned-prcomment: | + ## ✅ DCO Sign-off Verified + + All commits in this pull request are signed off. Thank you! From 3097c3f58bee561dd49fd283e1c30d6f091a1164 Mon Sep 17 00:00:00 2001 From: Amanda L Martin Date: Fri, 12 Jun 2026 14:30:11 -0400 Subject: [PATCH 2/2] dco slight edit --- .github/workflows/dco.yml | 272 +++++++++++++++++++++++++------------- 1 file changed, 180 insertions(+), 92 deletions(-) diff --git a/.github/workflows/dco.yml b/.github/workflows/dco.yml index 02c0398..e3677f0 100644 --- a/.github/workflows/dco.yml +++ b/.github/workflows/dco.yml @@ -1,103 +1,191 @@ -# ───────────────────────────────────────────────────────────────────────────── -# DCO (Developer Certificate of Origin) Enforcement Workflow +```yaml +# DCO (Developer Certificate of Origin) enforcement # -# Place this file at: .github/workflows/dco.yml -# in the canton-network/.github repo to enforce org-wide. +# This workflow: +# - Checks every commit in a pull request +# - Requires at least one valid "Signed-off-by: Name " trailer +# - Reports the exact commits that fail +# - Uses read-only permissions +# - Does not check out or execute pull-request code # -# What it does: -# - Runs on every pull request (opened, updated, or synchronized) -# - Checks that EVERY commit in the PR contains a "Signed-off-by:" trailer -# - Fails the required status check if any commit is missing the sign-off -# - Leaves a clear comment on the PR explaining how to fix it -# ───────────────────────────────────────────────────────────────────────────── - -name: DCO Check - -# The contributor-assistant action requires BOTH pull_request_target AND -# issue_comment triggers to function correctly: -# - pull_request_target → fires when a PR is opened/updated -# - issue_comment → fires when a contributor types 'recheck' +# For organization-wide enforcement, store this workflow in the +# organization's workflow-policy repository and select it under: +# Organization Settings → Rulesets → Require workflows to pass before merging + +name: DCO Sign-off + on: pull_request_target: - types: [opened, synchronize, reopened] - issue_comment: - types: [created] + +permissions: + pull-requests: read + +concurrency: + group: dco-${{ github.repository }}-${{ github.event.pull_request.number }} + cancel-in-progress: true jobs: dco: - name: Require Signed-off-by on all commits + name: Verify DCO sign-offs runs-on: ubuntu-latest - # Permissions required by contributor-assistant/github-action - permissions: - actions: write # re-run checks - contents: write # read commit history - pull-requests: write # post help comments on the PR - statuses: write # set the commit status check (✅/❌) - steps: - # ── Check DCO sign-off on every commit in the PR ───────────────────── - - name: Check DCO sign-off - # Only run when a PR is opened/updated, OR when a contributor - # comments 'recheck' to manually re-trigger the check. - if: > - github.event_name == 'pull_request_target' || - github.event.comment.body == 'recheck' - uses: contributor-assistant/github-action@v2.6.1 + - name: Check every pull-request commit + shell: bash env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - # ── DCO mode ──────────────────────────────────────────────────── - # use-dco-flag switches the action from CLA click-through mode - # into DCO mode: every commit must have a Signed-off-by trailer. - use-dco-flag: true - - # path-to-signatures is required by the action even in DCO mode; - # point it at a harmless path — it won't be written to. - path-to-signatures: 'dco-signatures/signatures.json' - - # branch where the (unused) signature file would live - branch: 'main' - - # ── Allow bot commits to skip the sign-off check ───────────── - # Automated commits from Dependabot, Renovate, etc. cannot add - # a Signed-off-by trailer, so we exempt them here. - allowlist: bot,dependabot[bot],renovate[bot],github-actions[bot] - - # ── Custom PR comment when sign-off is missing ─────────────── - custom-notsigned-prcomment: | - ## ❌ DCO Sign-off Missing - - One or more commits in this pull request are missing a - `Signed-off-by` trailer. This is required by the - [Developer Certificate of Origin (DCO)](https://developercertificate.org/). - - ### How to fix it - - **Option A – amend the last commit (single-commit PR):** - ```bash - git commit --amend --signoff - git push --force-with-lease - ``` - - **Option B – rebase and sign off all commits at once:** - ```bash - git rebase HEAD~ --signoff - git push --force-with-lease - ``` - - **Option C – never forget again (global git config):** - ```bash - git config --global format.signoff true - ``` - - > The trailer should look like: - > `Signed-off-by: Your Name ` - > - > Once fixed, comment `recheck` on this PR to re-run the check. - - # ── Custom PR comment when all commits are signed off ──────── - custom-allsigned-prcomment: | - ## ✅ DCO Sign-off Verified - - All commits in this pull request are signed off. Thank you! + GH_TOKEN: ${{ github.token }} + REPOSITORY: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + + run: | + set -Eeuo pipefail + + api_get() { + curl \ + --silent \ + --show-error \ + --fail-with-body \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${GH_TOKEN}" \ + --header "X-GitHub-Api-Version: 2026-03-10" \ + "$1" + } + + pr_url="https://api.github.com/repos/${REPOSITORY}/pulls/${PR_NUMBER}" + pr_json="$(api_get "${pr_url}")" + total_commits="$(jq -r '.commits' <<<"${pr_json}")" + + if [[ ! "${total_commits}" =~ ^[0-9]+$ ]]; then + echo "::error title=DCO check failed::Could not determine the number of commits in this pull request." + exit 1 + fi + + # GitHub's pull-request commits endpoint returns at most 250 + # commits. Fail closed instead of silently skipping commits. + if (( total_commits > 250 )); then + { + echo "## ❌ DCO check could not complete" + echo + echo "This pull request contains ${total_commits} commits." + echo + echo "The GitHub pull-request commits API returns at most 250 commits." + echo "Please split this pull request into smaller pull requests." + } >>"${GITHUB_STEP_SUMMARY}" + + echo "::error title=Too many commits::This pull request has ${total_commits} commits. Split it into pull requests containing no more than 250 commits." + exit 1 + fi + + commits_file="$(mktemp)" + missing_file="$(mktemp)" + : >"${commits_file}" + : >"${missing_file}" + + # Retrieve all available commits, 100 per page. + for page in 1 2 3; do + page_url="${pr_url}/commits?per_page=100&page=${page}" + page_json="$(api_get "${page_url}")" + page_count="$(jq 'length' <<<"${page_json}")" + + jq -c '.[]' <<<"${page_json}" >>"${commits_file}" + + if (( page_count < 100 )); then + break + fi + done + + listed_commits="$( + wc -l <"${commits_file}" | tr -d '[:space:]' + )" + + # Fail closed if the API result is incomplete for any reason. + if (( listed_commits != total_commits )); then + { + echo "## ❌ DCO check could not complete" + echo + echo "Expected ${total_commits} commits but received ${listed_commits}." + echo + echo "No commits were approved because the complete pull request could not be evaluated." + } >>"${GITHUB_STEP_SUMMARY}" + + echo "::error title=Incomplete commit list::Expected ${total_commits} commits but received ${listed_commits}." + exit 1 + fi + + # git interpret-trailers limits the check to the trailer block at + # the end of the commit message. This prevents arbitrary text in + # the commit body from being mistaken for a DCO sign-off. + while IFS= read -r commit; do + sha="$(jq -r '.sha' <<<"${commit}")" + message="$(jq -r '.commit.message' <<<"${commit}")" + + trailers="$( + printf '%s\n' "${message}" | + git interpret-trailers --parse + )" + + if ! grep -Eiq \ + '^Signed-off-by:[[:space:]]+[^<>[:space:]][^<>]*[[:space:]]+<[^<>[:space:]]+@[^<>[:space:]]+>[[:space:]]*$' \ + <<<"${trailers}"; then + printf '%s\n' "${sha}" >>"${missing_file}" + fi + done <"${commits_file}" + + missing_count="$( + wc -l <"${missing_file}" | tr -d '[:space:]' + )" + + if (( missing_count > 0 )); then + { + echo "## ❌ DCO sign-off missing" + echo + echo "${missing_count} of ${total_commits} commits do not contain a valid:" + echo + echo ' Signed-off-by: Your Name ' + echo + echo "### Affected commits" + echo + + while IFS= read -r sha; do + short_sha="${sha:0:12}" + printf -- \ + '- [`%s`](https://github.com/%s/commit/%s)\n' \ + "${short_sha}" \ + "${REPOSITORY}" \ + "${sha}" + done <"${missing_file}" + + echo + echo "### Fix the most recent commit" + echo + echo "~~~bash" + echo "git commit --amend --signoff" + echo "git push --force-with-lease" + echo "~~~" + echo + echo "### Fix multiple commits" + echo + echo "Replace N with the number of commits in your pull request:" + echo + echo "~~~bash" + echo "git rebase --signoff HEAD~N" + echo "git push --force-with-lease" + echo "~~~" + echo + echo "The check will run again automatically after the corrected commits are pushed." + } >>"${GITHUB_STEP_SUMMARY}" + + while IFS= read -r sha; do + short_sha="${sha:0:12}" + echo "::error title=Missing DCO sign-off::Commit ${short_sha} does not contain a valid Signed-off-by trailer." + done <"${missing_file}" + + exit 1 + fi + + { + echo "## ✅ DCO sign-off verified" + echo + echo "All ${total_commits} commits contain a valid Signed-off-by trailer." + } >>"${GITHUB_STEP_SUMMARY}" +```