fix(attribution): reject wrapped bylines (#90) #188
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: no-ai-attribution | |
| # CI-side mirror of .githooks/pre-commit and .githooks/commit-msg, using the | |
| # same context-aware scanner. The local hooks only run for a clone pointed | |
| # at them with `git config core.hooksPath .githooks`, and `git commit | |
| # --no-verify` bypasses them. Requiring this context means that local bypass | |
| # alone is insufficient, but the pull-request run still executes the | |
| # candidate tree's workflow and scanner. Its preventive trust boundary is | |
| # owner review of every change to these controls; the separate protected-path | |
| # workflow is only an advisory signal, not a required external policy engine. | |
| # | |
| # No path is exempt. The scanner keeps its actor lexicon separate from its | |
| # credit grammar, so its own implementation remains clean under its policy. | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, edited] | |
| push: | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| jobs: | |
| attribution: | |
| name: attribution | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| # The commit-message half of the scan walks a range of commits, not | |
| # just the tip, so the full history is required. | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Diff and commit messages carry no AI attribution | |
| # Passed through the environment rather than interpolated into the | |
| # script body, which is the general rule for anything from the event | |
| # payload. Both are empty on push events and unused there. | |
| env: | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| PR_TITLE: ${{ github.event.pull_request.title }} | |
| PR_BODY: ${{ github.event.pull_request.body }} | |
| run: | | |
| set -euo pipefail | |
| if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then | |
| for sha in "$BASE_SHA" "$HEAD_SHA"; do | |
| if ! printf '%s\n' "$sha" \ | |
| | grep -Eq '^[0-9a-f]{40}([0-9a-f]{24})?$'; then | |
| echo "pull-request base/head SHA metadata is missing or malformed" >&2 | |
| exit 1 | |
| fi | |
| done | |
| git cat-file -e "$BASE_SHA^{commit}" | |
| git cat-file -e "$HEAD_SHA^{commit}" | |
| # PR metadata is mutable independently of HEAD. Rechecking edits is | |
| # an advisory tripwire, not an immutable merge-time proof. | |
| printf '%s\n%s\n' "$PR_TITLE" "$PR_BODY" \ | |
| | python3 -I .github/scripts/check_attribution.py \ | |
| --stdin '<pull-request-metadata>' --input-format markdown | |
| python3 -I .github/scripts/check_attribution.py --tree "$HEAD_SHA" | |
| python3 -I .github/scripts/check_attribution.py \ | |
| --commits "$BASE_SHA..$HEAD_SHA" | |
| elif [ "$GITHUB_EVENT_NAME" = "push" ]; then | |
| # Push to main: scan the whole tracked tree and the whole history, | |
| # so a bypass that reached main is caught even if it never went | |
| # through a pull request. | |
| python3 -I .github/scripts/check_attribution.py --tree "$GITHUB_SHA" | |
| python3 -I .github/scripts/check_attribution.py --commits "$GITHUB_SHA" | |
| else | |
| echo "unexpected attribution event: $GITHUB_EVENT_NAME" >&2 | |
| exit 1 | |
| fi | |
| echo "clean" |