diff --git a/.github/scripts/check-no-ai-attribution.sh b/.github/scripts/check-no-ai-attribution.sh index 76eaba1..05fc634 100755 --- a/.github/scripts/check-no-ai-attribution.sh +++ b/.github/scripts/check-no-ai-attribution.sh @@ -15,6 +15,17 @@ # actions@github.com emails # - names containing claude/copilot/devin/aider/codex/gemini # +# One carve-out, and only from the identity rules: a named dependency bot. +# The policy exists so that a human is not displaced as the author of record, +# and a dependency bump has no human author to displace — Dependabot is not an +# assistant that helped somebody write something, it is the whole author. The +# message rules still apply to its commits in full, so a bot cannot carry an +# AI co-author trailer or a "Generated with" watermark in past this. +# +# It is a hygiene guard, not a security boundary: anyone can set an author +# email locally. What stops a forged one is that it still has to survive +# review and a merge, which is where attribution is actually judged. +# # Usage: # .github/scripts/check-no-ai-attribution.sh .. # @@ -24,6 +35,12 @@ # git commit --allow-empty -s -m "test: bad commit" \ # -m "Co-Authored-By: Example Bot " # .github/scripts/check-no-ai-attribution.sh main..HEAD # must fail once +# # and the dependency-bot carve-out, which must pass on identity but still +# # fail on a watermark in the message: +# git -c user.name='dependabot[bot]' \ +# -c user.email='49699333+dependabot[bot]@users.noreply.github.com' \ +# commit --allow-empty -m "build(deps): bump x" -m "Signed-off-by: dependabot[bot] " +# .github/scripts/check-no-ai-attribution.sh main..HEAD # still just the one failure # git checkout - && git branch -D scratch/attribution set -euo pipefail @@ -42,6 +59,13 @@ msg_patterns=( email_pattern='(\[bot\]@users\.noreply\.github\.com$|@noreply\.anthropic\.com$|^actions@github\.com$)' name_pattern='\b(claude|copilot|devin|aider|codex|gemini)\b' +# Automation identities exempt from the *identity* rules above (never from the +# message rules). Anchored at both ends and matched against the whole +# lowercased address: a substring rule here would be a hole a crafted local +# part could walk through. Matched by name rather than by GitHub's numeric user +# id, so the exemption survives an id change; add another bot by naming it. +trusted_bot_emails='^([0-9]+\+)?(dependabot|dependabot-preview)\[bot\]@users\.noreply\.github\.com$' + fail=0 count=0 while IFS= read -r sha; do @@ -67,6 +91,11 @@ while IFS= read -r sha; do name="$(git log -1 --format='%cn' "$sha")" email="$(git log -1 --format='%ce' "$sha")" fi + # Skip the identity rules for a named dependency bot in this role. The + # message rules ran above and applied to this commit like any other. + if printf '%s\n' "$email" | tr '[:upper:]' '[:lower:]' | grep -Eq "$trusted_bot_emails"; then + continue + fi if printf '%s\n' "$email" | tr '[:upper:]' '[:lower:]' | grep -Eq "$email_pattern"; then echo "::error::Commit ${sha} ${role} email '${email}' is a bot/vendor identity." fail=1 diff --git a/AGENTS.md b/AGENTS.md index e3c0d62..fd61bfd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -70,7 +70,8 @@ and says so in its CONTRIBUTING. Use whatever helps. - no `Co-Authored-By:` trailer naming an assistant, a model, or a vendor, - no "Generated with …" footer, no robot emoji, -- no bot account as author or committer. +- no bot account as author or committer — save a named dependency bot, which + is exempt from the identity rule only and still checked on its message. The human who opens the pull request is the author of record and takes responsibility for the change under the DCO. That is what the sign-off diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7acbb66..859c383 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -98,9 +98,10 @@ agents on the layout, the commands, and the house style. **AI attribution is not welcome.** No `Co-Authored-By` trailer naming an assistant, model or vendor; no "Generated with …" footer; no robot emoji; no -bot identity as author or committer. Whoever opens the pull request is the -author of record, takes responsibility under the DCO, and the history should -say so — a tool cannot certify the DCO, which is the whole point of it. +bot identity as author or committer, save the one carve-out below. Whoever +opens the pull request is the author of record, takes responsibility under the +DCO, and the history should say so — a tool cannot certify the DCO, which is +the whole point of it. This is enforced, not requested: `commit-policy.yml` runs [`check-no-ai-attribution.sh`](.github/scripts/check-no-ai-attribution.sh) and @@ -124,6 +125,15 @@ git push --force-with-lease repository settings. That is a courtesy; the check in CI is the boundary. Contributions authored *by* an autonomous account are not accepted. +**One carve-out: a named dependency bot.** Dependabot is exempt from the +*identity* half of the check and from nothing else. The rule exists so that a +human is not displaced as the author of record, and a version bump has no +human to displace — it is not somebody's work with the credit misassigned. The +message rules still apply in full, so a bot cannot carry an AI co-author +trailer, a "Generated with" footer or a robot emoji past the check either. +Adding another bot means naming it in `check-no-ai-attribution.sh`: the +allowlist is a list on purpose, so that widening it is a visible decision. + ## 7. PR flow - Branch from `main`; name branches `feat/…`, `fix/…`, `docs/…`, `ci/…`.