Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1fba1e06b2 | ||
|
|
c09fc72500 | ||
|
|
6fe42b2ad2 | ||
|
|
76c72b02e2 |
@@ -36,7 +36,6 @@ jobs:
|
|||||||
head-branch: ${{ github.head_ref }}
|
head-branch: ${{ github.head_ref }}
|
||||||
pr-title: ${{ github.event.pull_request.title }}
|
pr-title: ${{ github.event.pull_request.title }}
|
||||||
pr-author: ${{ github.event.pull_request.user.login }}
|
pr-author: ${{ github.event.pull_request.user.login }}
|
||||||
pr-body: ${{ github.event.pull_request.body }}
|
|
||||||
server-url: ${{ github.server_url }}
|
server-url: ${{ github.server_url }}
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
base-sha: ${{ github.event.pull_request.base.sha }}
|
base-sha: ${{ github.event.pull_request.base.sha }}
|
||||||
@@ -53,7 +52,6 @@ job — the step still fails, but the job cannot block the PR.
|
|||||||
| `head-branch` | yes | Head branch name — `github.head_ref` |
|
| `head-branch` | yes | Head branch name — `github.head_ref` |
|
||||||
| `pr-title` | yes | PR title — `github.event.pull_request.title` |
|
| `pr-title` | yes | PR title — `github.event.pull_request.title` |
|
||||||
| `pr-author` | no | PR author login — `github.event.pull_request.user.login`; `dfritz` is exempt |
|
| `pr-author` | no | PR author login — `github.event.pull_request.user.login`; `dfritz` is exempt |
|
||||||
| `pr-body` | yes | PR description — `github.event.pull_request.body` |
|
|
||||||
| `server-url` | yes | Gitea server URL — `github.server_url` |
|
| `server-url` | yes | Gitea server URL — `github.server_url` |
|
||||||
| `token` | yes | Gitea Actions token — `github.token` |
|
| `token` | yes | Gitea Actions token — `github.token` |
|
||||||
| `base-sha` | yes | Base commit — `github.event.pull_request.base.sha` |
|
| `base-sha` | yes | Base commit — `github.event.pull_request.base.sha` |
|
||||||
@@ -77,8 +75,9 @@ The check validates four things for every non-break-glass Agent PR:
|
|||||||
`Fixes bug-<id>` automation token and the matching navigable
|
`Fixes bug-<id>` automation token and the matching navigable
|
||||||
`https://agenthub.fritzlab.net/bug-<id>` URL. Every PR has a separate
|
`https://agenthub.fritzlab.net/bug-<id>` URL. Every PR has a separate
|
||||||
`## Attribution` section containing the canonical `Authored-By`
|
`## Attribution` section containing the canonical `Authored-By`
|
||||||
product/model watermark. The action asks Gitea to render the body with a
|
product/model watermark. The action loads the body from Gitea by repository
|
||||||
bounded 5-second connection and 15-second total wait, then checks visible
|
and PR number, then asks Gitea to render it; both calls have a bounded
|
||||||
|
5-second connection and 15-second total wait. It then checks visible
|
||||||
`<h2>` sections outside collapsed `<details>` content. Fenced, commented,
|
`<h2>` sections outside collapsed `<details>` content. Fenced, commented,
|
||||||
scripted, or collapsed copies do not satisfy the visible provenance contract.
|
scripted, or collapsed copies do not satisfy the visible provenance contract.
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -26,9 +26,6 @@ inputs:
|
|||||||
dfritz is exempt from all checks.
|
dfritz is exempt from all checks.
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
pr-body:
|
|
||||||
description: PR description — github.event.pull_request.body.
|
|
||||||
required: true
|
|
||||||
server-url:
|
server-url:
|
||||||
description: Gitea server URL — github.server_url.
|
description: Gitea server URL — github.server_url.
|
||||||
required: true
|
required: true
|
||||||
@@ -51,9 +48,10 @@ runs:
|
|||||||
HEAD_BRANCH: ${{ inputs.head-branch }}
|
HEAD_BRANCH: ${{ inputs.head-branch }}
|
||||||
PR_TITLE: ${{ inputs.pr-title }}
|
PR_TITLE: ${{ inputs.pr-title }}
|
||||||
PR_AUTHOR: ${{ inputs.pr-author }}
|
PR_AUTHOR: ${{ inputs.pr-author }}
|
||||||
PR_BODY: ${{ inputs.pr-body }}
|
|
||||||
GITEA_SERVER_URL: ${{ inputs.server-url }}
|
GITEA_SERVER_URL: ${{ inputs.server-url }}
|
||||||
GITEA_TOKEN: ${{ inputs.token }}
|
GITEA_TOKEN: ${{ inputs.token }}
|
||||||
|
GITEA_REPOSITORY: ${{ github.repository }}
|
||||||
|
GITEA_PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
BASE_SHA: ${{ inputs.base-sha }}
|
BASE_SHA: ${{ inputs.base-sha }}
|
||||||
HEAD_SHA: ${{ inputs.head-sha }}
|
HEAD_SHA: ${{ inputs.head-sha }}
|
||||||
run: bash "${{ github.action_path }}/check.sh"
|
run: bash "${{ github.action_path }}/check.sh"
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ set -euo pipefail
|
|||||||
BRANCH="${HEAD_BRANCH:-}"
|
BRANCH="${HEAD_BRANCH:-}"
|
||||||
TITLE="${PR_TITLE:-}"
|
TITLE="${PR_TITLE:-}"
|
||||||
AUTHOR="${PR_AUTHOR:-}"
|
AUTHOR="${PR_AUTHOR:-}"
|
||||||
BODY="${PR_BODY:-}"
|
|
||||||
BASE="${BASE_SHA:-}"
|
BASE="${BASE_SHA:-}"
|
||||||
HEAD="${HEAD_SHA:-}"
|
HEAD="${HEAD_SHA:-}"
|
||||||
SERVER_URL="${GITEA_SERVER_URL:-}"
|
SERVER_URL="${GITEA_SERVER_URL:-}"
|
||||||
TOKEN="${GITEA_TOKEN:-}"
|
TOKEN="${GITEA_TOKEN:-}"
|
||||||
|
REPOSITORY="${GITEA_REPOSITORY:-}"
|
||||||
|
PR_NUMBER="${GITEA_PR_NUMBER:-}"
|
||||||
|
|
||||||
# Break-glass: dfritz is exempt from all naming checks.
|
# Break-glass: dfritz is exempt from all naming checks.
|
||||||
if [ "${AUTHOR}" = "dfritz" ]; then
|
if [ "${AUTHOR}" = "dfritz" ]; then
|
||||||
@@ -26,8 +27,19 @@ BRANCH_BUG=""
|
|||||||
# Gitea's renderer is the visibility contract. Validate its output instead of
|
# Gitea's renderer is the visibility contract. Validate its output instead of
|
||||||
# maintaining a second Markdown parser in this action.
|
# maintaining a second Markdown parser in this action.
|
||||||
RENDERED_BODY=""
|
RENDERED_BODY=""
|
||||||
if [ -z "${SERVER_URL}" ] || [ -z "${TOKEN}" ]; then
|
BODY=""
|
||||||
echo "FAIL[check-naming]: server-url and token are required to render the PR body"
|
if [ -z "${SERVER_URL}" ] || [ -z "${TOKEN}" ] ||
|
||||||
|
! printf '%s' "${REPOSITORY}" | grep -qE '^[[:alnum:]_.-]+/[[:alnum:]_.-]+$' ||
|
||||||
|
! printf '%s' "${PR_NUMBER}" | grep -qE '^[1-9][0-9]*$'; then
|
||||||
|
echo "FAIL[check-naming]: server-url, token, repository, and PR number are required to load the PR body"
|
||||||
|
FAILED=1
|
||||||
|
elif ! BODY=$(curl --fail --silent --show-error \
|
||||||
|
--connect-timeout 5 \
|
||||||
|
--max-time 15 \
|
||||||
|
--header "Authorization: token ${TOKEN}" \
|
||||||
|
"${SERVER_URL%/}/api/v1/repos/${REPOSITORY}/pulls/${PR_NUMBER}" |
|
||||||
|
jq -er '.body | if type == "string" then . else error("body is not a string") end'); then
|
||||||
|
echo "FAIL[check-naming]: Gitea could not load the PR body"
|
||||||
FAILED=1
|
FAILED=1
|
||||||
elif ! RENDERED_BODY=$(printf '%s' "${BODY}" |
|
elif ! RENDERED_BODY=$(printf '%s' "${BODY}" |
|
||||||
jq -Rs '{Text: ., Mode: "gfm"}' |
|
jq -Rs '{Text: ., Mode: "gfm"}' |
|
||||||
@@ -109,7 +121,7 @@ if echo "${TITLE}" | grep -qE "^\[bug-[a-z0-9]+\] ."; then
|
|||||||
TITLE_BUG=$(echo "${TITLE}" | sed -E 's|^\[(bug-[a-z0-9]+)\].*|\1|')
|
TITLE_BUG=$(echo "${TITLE}" | sed -E 's|^\[(bug-[a-z0-9]+)\].*|\1|')
|
||||||
fi
|
fi
|
||||||
|
|
||||||
TITLE_DESCRIPTION=$(printf '%s\n' "${TITLE}" | sed -E 's/^\[bug-[a-z0-9]+\] //')
|
TITLE_DESCRIPTION=$(printf '%s\n' "${TITLE}" | sed -E 's/^\[bug-[a-z0-9]+\][[:space:]]+//')
|
||||||
if printf '%s\n' "${TITLE_DESCRIPTION}" | grep -qE '^[[:alnum:]-]+(\([^)]*\))?!?:[[:space:]]'; then
|
if printf '%s\n' "${TITLE_DESCRIPTION}" | grep -qE '^[[:alnum:]-]+(\([^)]*\))?!?:[[:space:]]'; then
|
||||||
echo "FAIL[check-naming]: title must use a plain-language imperative without Conventional Commit syntax"
|
echo "FAIL[check-naming]: title must use a plain-language imperative without Conventional Commit syntax"
|
||||||
FAILED=1
|
FAILED=1
|
||||||
|
|||||||
+24
-5
@@ -4,6 +4,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
connect_timeout=0
|
connect_timeout=0
|
||||||
total_timeout=0
|
total_timeout=0
|
||||||
|
url=""
|
||||||
while [ "$#" -gt 0 ]; do
|
while [ "$#" -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--connect-timeout)
|
--connect-timeout)
|
||||||
@@ -16,20 +17,38 @@ while [ "$#" -gt 0 ]; do
|
|||||||
total_timeout=1
|
total_timeout=1
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
|
--header|--data-binary)
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
http*)
|
||||||
|
url="$1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
*) shift ;;
|
*) shift ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
[ "${connect_timeout}" -eq 1 ] && [ "${total_timeout}" -eq 1 ] || exit 2
|
[ "${connect_timeout}" -eq 1 ] && [ "${total_timeout}" -eq 1 ] || exit 2
|
||||||
|
|
||||||
if printf '%s\n' "${PR_BODY}" | grep -Fq '[[stall-renderer]]'; then
|
if [[ "${url}" == */api/v1/repos/action/check-naming/pulls/1 ]]; then
|
||||||
exit 28
|
if [ -n "${TEST_PR_BODY_FILE:-}" ]; then
|
||||||
|
jq -Rs '{body: .}' < "${TEST_PR_BODY_FILE}"
|
||||||
|
else
|
||||||
|
printf '%s' "${PR_BODY:-}" | jq -Rs '{body: .}'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if printf '%s\n' "${PR_BODY}" | grep -Fq '<!--'; then
|
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf '%s\n' "${PR_BODY}" | awk '
|
BODY=$(jq -r '.Text')
|
||||||
|
|
||||||
|
if printf '%s\n' "${BODY}" | grep -Fq '[[stall-renderer]]'; then
|
||||||
|
exit 28
|
||||||
|
fi
|
||||||
|
|
||||||
|
if printf '%s\n' "${BODY}" | grep -Fq '<!--'; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "${BODY}" | awk '
|
||||||
{
|
{
|
||||||
line=$0
|
line=$0
|
||||||
lower=tolower(line)
|
lower=tolower(line)
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ check() {
|
|||||||
body=$(canonical_body "${bug:-bug-test}")
|
body=$(canonical_body "${bug:-bug-test}")
|
||||||
local out rc=0
|
local out rc=0
|
||||||
out=$(cd "${FIXTURES}" && HEAD_BRANCH="$branch" PR_TITLE="$title" PR_AUTHOR="$author" \
|
out=$(cd "${FIXTURES}" && HEAD_BRANCH="$branch" PR_TITLE="$title" PR_AUTHOR="$author" \
|
||||||
PR_BODY="$body" GITEA_SERVER_URL="https://code.test" GITEA_TOKEN="test-token" PATH="${TEST_BIN}:$PATH" \
|
PR_BODY="$body" GITEA_SERVER_URL="https://code.test" GITEA_TOKEN="test-token" \
|
||||||
|
GITEA_REPOSITORY="action/check-naming" GITEA_PR_NUMBER=1 PATH="${TEST_BIN}:$PATH" \
|
||||||
BASE_SHA="$BASE" HEAD_SHA="$GOOD_HEAD" bash "$SCRIPT" 2>&1) || rc=$?
|
BASE_SHA="$BASE" HEAD_SHA="$GOOD_HEAD" bash "$SCRIPT" 2>&1) || rc=$?
|
||||||
local got_lines=0
|
local got_lines=0
|
||||||
echo "$out" | grep -qE "^FAIL" && got_lines=1 || true
|
echo "$out" | grep -qE "^FAIL" && got_lines=1 || true
|
||||||
@@ -48,7 +49,8 @@ check_contract() {
|
|||||||
local out rc=0
|
local out rc=0
|
||||||
out=$(cd "${FIXTURES}" && HEAD_BRANCH="architect/bug-x7k2m9/contract" \
|
out=$(cd "${FIXTURES}" && HEAD_BRANCH="architect/bug-x7k2m9/contract" \
|
||||||
PR_TITLE="[bug-x7k2m9] Enforce contract" PR_AUTHOR="architect" \
|
PR_TITLE="[bug-x7k2m9] Enforce contract" PR_AUTHOR="architect" \
|
||||||
PR_BODY="$body" GITEA_SERVER_URL="https://code.test" GITEA_TOKEN="test-token" PATH="${TEST_BIN}:$PATH" \
|
PR_BODY="$body" GITEA_SERVER_URL="https://code.test" GITEA_TOKEN="test-token" \
|
||||||
|
GITEA_REPOSITORY="action/check-naming" GITEA_PR_NUMBER=1 PATH="${TEST_BIN}:$PATH" \
|
||||||
BASE_SHA="$base" HEAD_SHA="$head" bash "$SCRIPT" 2>&1) || rc=$?
|
BASE_SHA="$base" HEAD_SHA="$head" bash "$SCRIPT" 2>&1) || rc=$?
|
||||||
if [ "$rc" -eq 1 ] && printf '%s\n' "$out" | grep -Fq "$diagnostic"; then
|
if [ "$rc" -eq 1 ] && printf '%s\n' "$out" | grep -Fq "$diagnostic"; then
|
||||||
echo "PASS [$desc]"
|
echo "PASS [$desc]"
|
||||||
@@ -65,7 +67,8 @@ check_contract_pass() {
|
|||||||
local out rc=0
|
local out rc=0
|
||||||
out=$(cd "${FIXTURES}" && HEAD_BRANCH="architect/bug-x7k2m9/contract" \
|
out=$(cd "${FIXTURES}" && HEAD_BRANCH="architect/bug-x7k2m9/contract" \
|
||||||
PR_TITLE="[bug-x7k2m9] Enforce contract" PR_AUTHOR="architect" \
|
PR_TITLE="[bug-x7k2m9] Enforce contract" PR_AUTHOR="architect" \
|
||||||
PR_BODY="$body" GITEA_SERVER_URL="https://code.test" GITEA_TOKEN="test-token" PATH="${TEST_BIN}:$PATH" \
|
PR_BODY="$body" GITEA_SERVER_URL="https://code.test" GITEA_TOKEN="test-token" \
|
||||||
|
GITEA_REPOSITORY="action/check-naming" GITEA_PR_NUMBER=1 PATH="${TEST_BIN}:$PATH" \
|
||||||
BASE_SHA="$base" HEAD_SHA="$head" bash "$SCRIPT" 2>&1) || rc=$?
|
BASE_SHA="$base" HEAD_SHA="$head" bash "$SCRIPT" 2>&1) || rc=$?
|
||||||
if [ "$rc" -eq 0 ] && printf '%s\n' "$out" | grep -Fq "check-naming: ok"; then
|
if [ "$rc" -eq 0 ] && printf '%s\n' "$out" | grep -Fq "check-naming: ok"; then
|
||||||
echo "PASS [$desc]"
|
echo "PASS [$desc]"
|
||||||
@@ -82,7 +85,8 @@ check_chore_contract() {
|
|||||||
local out rc=0
|
local out rc=0
|
||||||
out=$(cd "${FIXTURES}" && HEAD_BRANCH="chore/contract" \
|
out=$(cd "${FIXTURES}" && HEAD_BRANCH="chore/contract" \
|
||||||
PR_TITLE="Improve delivery contract" PR_AUTHOR="dev" \
|
PR_TITLE="Improve delivery contract" PR_AUTHOR="dev" \
|
||||||
PR_BODY="$body" GITEA_SERVER_URL="https://code.test" GITEA_TOKEN="test-token" PATH="${TEST_BIN}:$PATH" \
|
PR_BODY="$body" GITEA_SERVER_URL="https://code.test" GITEA_TOKEN="test-token" \
|
||||||
|
GITEA_REPOSITORY="action/check-naming" GITEA_PR_NUMBER=1 PATH="${TEST_BIN}:$PATH" \
|
||||||
BASE_SHA="$base" HEAD_SHA="$head" bash "$SCRIPT" 2>&1) || rc=$?
|
BASE_SHA="$base" HEAD_SHA="$head" bash "$SCRIPT" 2>&1) || rc=$?
|
||||||
if [ "$rc" -eq 1 ] && printf '%s\n' "$out" | grep -Fq "$diagnostic"; then
|
if [ "$rc" -eq 1 ] && printf '%s\n' "$out" | grep -Fq "$diagnostic"; then
|
||||||
echo "PASS [$desc]"
|
echo "PASS [$desc]"
|
||||||
@@ -120,6 +124,7 @@ check "bug-id mismatch" 1 "dev/bug-x7k2m9/fix-resize" "[bug-
|
|||||||
# fail: chore branch with [bug-id] title prefix
|
# fail: chore branch with [bug-id] title prefix
|
||||||
check "chore with bug-id title" 1 "chore/bump-deps" "[bug-abc123] Bump deps" "dev"
|
check "chore with bug-id title" 1 "chore/bump-deps" "[bug-abc123] Bump deps" "dev"
|
||||||
check "role/bug Conventional title" 1 "dev/bug-x7k2m9/fix-resize" "[bug-x7k2m9] fix(hub): fix resize" "dev"
|
check "role/bug Conventional title" 1 "dev/bug-x7k2m9/fix-resize" "[bug-x7k2m9] fix(hub): fix resize" "dev"
|
||||||
|
check "role/bug Conventional title gap" 1 "dev/bug-x7k2m9/fix-resize" "[bug-x7k2m9] fix(hub): fix resize" "dev"
|
||||||
check "chore Conventional title" 1 "chore/bump-deps" "chore(deps): bump deps" "dev"
|
check "chore Conventional title" 1 "chore/bump-deps" "chore(deps): bump deps" "dev"
|
||||||
check "role/bug breaking title" 1 "dev/bug-x7k2m9/fix-resize" "[bug-x7k2m9] fix!: break resize" "dev"
|
check "role/bug breaking title" 1 "dev/bug-x7k2m9/fix-resize" "[bug-x7k2m9] fix!: break resize" "dev"
|
||||||
|
|
||||||
@@ -205,6 +210,25 @@ check_contract "stalled renderer fails closed at the curl timeout" \
|
|||||||
"$(canonical_body bug-x7k2m9)"$'\n[[stall-renderer]]' \
|
"$(canonical_body bug-x7k2m9)"$'\n[[stall-renderer]]' \
|
||||||
"$BASE" "$GOOD_HEAD" "Gitea could not render the PR body"
|
"$BASE" "$GOOD_HEAD" "Gitea could not render the PR body"
|
||||||
|
|
||||||
|
LARGE_BODY="${FIXTURES}/large-pr-body"
|
||||||
|
awk 'BEGIN { for (i=0; i<150000; i++) printf "x"; print "" }' > "${LARGE_BODY}"
|
||||||
|
canonical_body bug-x7k2m9 >> "${LARGE_BODY}"
|
||||||
|
large_out=""
|
||||||
|
large_rc=0
|
||||||
|
large_out=$(cd "${FIXTURES}" && HEAD_BRANCH="architect/bug-x7k2m9/contract" \
|
||||||
|
PR_TITLE="[bug-x7k2m9] Enforce contract" PR_AUTHOR="architect" \
|
||||||
|
TEST_PR_BODY_FILE="${LARGE_BODY}" GITEA_SERVER_URL="https://code.test" GITEA_TOKEN="test-token" \
|
||||||
|
GITEA_REPOSITORY="action/check-naming" GITEA_PR_NUMBER=1 PATH="${TEST_BIN}:$PATH" \
|
||||||
|
BASE_SHA="$BASE" HEAD_SHA="$GOOD_HEAD" bash "$SCRIPT" 2>&1) || large_rc=$?
|
||||||
|
if [ "${large_rc}" -eq 0 ] && printf '%s\n' "${large_out}" | grep -Fq "check-naming: ok"; then
|
||||||
|
echo "PASS [large PR body loads without environment transport]"
|
||||||
|
PASS=$((PASS + 1))
|
||||||
|
else
|
||||||
|
echo "FAIL [large PR body loads without environment transport]: expected exit=0 and check-naming: ok, got exit=${large_rc}"
|
||||||
|
echo " output: ${large_out}"
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
git -C "${FIXTURES}" commit --allow-empty -q -m "unwatermarked change"
|
git -C "${FIXTURES}" commit --allow-empty -q -m "unwatermarked change"
|
||||||
BAD_HEAD=$(git -C "${FIXTURES}" rev-parse HEAD)
|
BAD_HEAD=$(git -C "${FIXTURES}" rev-parse HEAD)
|
||||||
check_contract "agenthub 779 commits reject missing watermark" "$BODY" "$GOOD_HEAD" "$BAD_HEAD" "must end with an Authored-By"
|
check_contract "agenthub 779 commits reject missing watermark" "$BODY" "$GOOD_HEAD" "$BAD_HEAD" "must end with an Authored-By"
|
||||||
|
|||||||
Reference in New Issue
Block a user