[bug-yhg8dqypwmar] fix(check-naming): reject hidden provenance #4
@@ -18,6 +18,7 @@ Title: [bug-x7k2m9] Fix terminal resize loss (chore PRs: no [bug-id] prefix
|
|||||||
- `<bug-id>` is `bug-` followed by lowercase alphanumeric characters
|
- `<bug-id>` is `bug-` followed by lowercase alphanumeric characters
|
||||||
- `<kebab-description>` is lowercase alphanumeric with hyphens, starting with a letter or digit
|
- `<kebab-description>` is lowercase alphanumeric with hyphens, starting with a letter or digit
|
||||||
- When both the branch and the title carry a bug-id they **must match**
|
- When both the branch and the title carry a bug-id they **must match**
|
||||||
|
- Titles are plain-language imperatives without Conventional Commit syntax
|
||||||
- Break-glass: PRs authored by `dfritz` are exempt from all checks
|
- Break-glass: PRs authored by `dfritz` are exempt from all checks
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@@ -57,7 +58,7 @@ job — the step still fails, but the job cannot block the PR.
|
|||||||
|
|
||||||
## Behavior
|
## Behavior
|
||||||
|
|
||||||
The check validates four things for a Bug-backed Agent PR:
|
The check validates four things for every non-break-glass Agent PR:
|
||||||
|
|
||||||
1. **Branch form** — must be `<role>/bug-<id>/<kebab>` or `chore/<kebab>`.
|
1. **Branch form** — must be `<role>/bug-<id>/<kebab>` or `chore/<kebab>`.
|
||||||
Unknown roles, missing `bug-` segment, uppercase bug-ids, and empty
|
Unknown roles, missing `bug-` segment, uppercase bug-ids, and empty
|
||||||
@@ -65,14 +66,17 @@ The check validates four things for a Bug-backed Agent PR:
|
|||||||
|
|
||||||
2. **Title form** — for a `role/bug` branch the title must start with
|
2. **Title form** — for a `role/bug` branch the title must start with
|
||||||
`[bug-<id>] `. For a `chore` branch the title must have no `[bug-id]`
|
`[bug-<id>] `. For a `chore` branch the title must have no `[bug-id]`
|
||||||
prefix. If both carry a bug-id they must match.
|
prefix. If both carry a bug-id they must match. Neither form accepts
|
||||||
|
Conventional Commit syntax.
|
||||||
|
|
||||||
3. **Tracking and attribution** — `## Tracking` contains both the literal
|
3. **Tracking and attribution** — on Bug-backed work, `## Tracking` contains both the literal
|
||||||
`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. A separate `## Attribution`
|
`https://agenthub.fritzlab.net/bug-<id>` URL. Every PR has a separate
|
||||||
section contains the canonical `Authored-By` product/model watermark.
|
`## Attribution` section containing the canonical `Authored-By`
|
||||||
|
product/model watermark.
|
||||||
|
|
||||||
4. **Commit attribution** — every commit in `base-sha..head-sha` ends with
|
4. **Commit attribution** — every commit in `base-sha..head-sha`, including
|
||||||
|
commits on `chore/` branches, ends with
|
||||||
the canonical `Authored-By` trailer, separated from the message body by a
|
the canonical `Authored-By` trailer, separated from the message body by a
|
||||||
blank line. The naming job must check out full history before this action.
|
blank line. The naming job must check out full history before this action.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ description: |
|
|||||||
Standard:
|
Standard:
|
||||||
branch: <role>/bug-<id>/<kebab> e.g. dev/bug-x7k2m9/fix-terminal-resize
|
branch: <role>/bug-<id>/<kebab> e.g. dev/bug-x7k2m9/fix-terminal-resize
|
||||||
chore: chore/<kebab> bug-less trivia only
|
chore: chore/<kebab> bug-less trivia only
|
||||||
title: [bug-<id>] Description (chore PRs: no [bug-id] prefix)
|
title: [bug-<id>] Description (chore PRs: no [bug-id] prefix; no Conventional Commit syntax)
|
||||||
|
|
||||||
Role must be a roster handle: dev | ux | ops | security | perf | architect | support
|
Role must be a roster handle: dev | ux | ops | security | perf | architect | support
|
||||||
Break-glass: PRs authored by dfritz are exempt.
|
Break-glass: PRs authored by dfritz are exempt.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ VISIBLE_BODY=$(printf '%s\n' "${BODY}" | awk '
|
|||||||
{
|
{
|
||||||
line=$0
|
line=$0
|
||||||
fence_pos=1
|
fence_pos=1
|
||||||
while (fence_pos <= 4 && substr(line, fence_pos, 1) == " ") fence_pos++
|
while (fence_pos <= 3 && substr(line, fence_pos, 1) == " ") fence_pos++
|
||||||
fence_char=substr(line, fence_pos, 1)
|
fence_char=substr(line, fence_pos, 1)
|
||||||
fence_run=0
|
fence_run=0
|
||||||
if (fence_char == "`" || fence_char == "~") {
|
if (fence_char == "`" || fence_char == "~") {
|
||||||
@@ -58,7 +58,8 @@ VISIBLE_BODY=$(printf '%s\n' "${BODY}" | awk '
|
|||||||
close_pos=pos + ticks
|
close_pos=pos + ticks
|
||||||
found_close=0
|
found_close=0
|
||||||
while (close_pos <= length(line)) {
|
while (close_pos <= length(line)) {
|
||||||
|
|
|||||||
if (substr(line, close_pos, ticks) == substr(line, pos, ticks) &&
|
if (substr(line, close_pos - 1, 1) != "`" &&
|
||||||
|
substr(line, close_pos, ticks) == substr(line, pos, ticks) &&
|
||||||
substr(line, close_pos + ticks, 1) != "`") {
|
substr(line, close_pos + ticks, 1) != "`") {
|
||||||
found_close=1
|
found_close=1
|
||||||
break
|
break
|
||||||
@@ -116,6 +117,12 @@ 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
|
||||||
|
|
||||||
|
PLAIN_TITLE=$(printf '%s\n' "${TITLE}" | sed -E 's/^\[bug-[a-z0-9]+\][[:space:]]+//')
|
||||||
|
if printf '%s\n' "${PLAIN_TITLE}" | grep -qE '^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([^)]*\))?!?:[[:space:]]'; then
|
||||||
|
echo "FAIL[check-naming]: title must be a plain-language imperative without Conventional Commit syntax"
|
||||||
|
FAILED=1
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "${BRANCH_KIND}" = "role-bug" ]; then
|
if [ "${BRANCH_KIND}" = "role-bug" ]; then
|
||||||
if [ -z "${TITLE_BUG}" ]; then
|
if [ -z "${TITLE_BUG}" ]; then
|
||||||
echo "FAIL[check-naming]: title must lead with [${BRANCH_BUG}]"
|
echo "FAIL[check-naming]: title must lead with [${BRANCH_BUG}]"
|
||||||
@@ -146,6 +153,12 @@ if [ "${BRANCH_KIND}" = "role-bug" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
elif [ "${BRANCH_KIND}" = "chore" ] && [ -n "${TITLE_BUG}" ]; then
|
||||||
|
echo "FAIL[check-naming]: chore branch should not carry a [bug-id] title prefix"
|
||||||
|
FAILED=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${BRANCH_KIND}" != "invalid" ]; then
|
||||||
attribution=$(printf '%s\n' "${VISIBLE_BODY}" | awk '
|
attribution=$(printf '%s\n' "${VISIBLE_BODY}" | awk '
|
||||||
/^## Attribution[[:space:]]*$/ { in_section=1; next }
|
/^## Attribution[[:space:]]*$/ { in_section=1; next }
|
||||||
/^## / && in_section { exit }
|
/^## / && in_section { exit }
|
||||||
@@ -192,9 +205,6 @@ if [ "${BRANCH_KIND}" = "role-bug" ]; then
|
|||||||
echo "FAIL[check-naming]: base-sha and head-sha must be lowercase hexadecimal commit SHAs"
|
echo "FAIL[check-naming]: base-sha and head-sha must be lowercase hexadecimal commit SHAs"
|
||||||
FAILED=1
|
FAILED=1
|
||||||
fi
|
fi
|
||||||
elif [ "${BRANCH_KIND}" = "chore" ] && [ -n "${TITLE_BUG}" ]; then
|
|
||||||
echo "FAIL[check-naming]: chore branch should not carry a [bug-id] title prefix"
|
|
||||||
FAILED=1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${FAILED}" -eq 0 ]; then
|
if [ "${FAILED}" -eq 0 ]; then
|
||||||
|
|||||||
@@ -73,6 +73,22 @@ check_contract_pass() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check_chore_contract() {
|
||||||
|
local desc="$1" body="$2" base="$3" head="$4" diagnostic="$5"
|
||||||
|
local out rc=0
|
||||||
|
out=$(cd "${FIXTURES}" && HEAD_BRANCH="chore/contract" \
|
||||||
|
PR_TITLE="Improve delivery contract" PR_AUTHOR="dev" \
|
||||||
|
PR_BODY="$body" BASE_SHA="$base" HEAD_SHA="$head" bash "$SCRIPT" 2>&1) || rc=$?
|
||||||
|
if [ "$rc" -eq 1 ] && printf '%s\n' "$out" | grep -Fq "$diagnostic"; then
|
||||||
|
echo "PASS [$desc]"
|
||||||
|
PASS=$((PASS + 1))
|
||||||
|
else
|
||||||
|
echo "FAIL [$desc]: expected exit=1 and diagnostic '$diagnostic', got exit=$rc"
|
||||||
|
echo " output: $out"
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# break-glass
|
# break-glass
|
||||||
check "dfritz exempt — invalid branch" 0 "totally/wrong-branch" "no prefix" "dfritz"
|
check "dfritz exempt — invalid branch" 0 "totally/wrong-branch" "no prefix" "dfritz"
|
||||||
check "dfritz exempt — mismatch" 0 "dev/bug-abc/thing" "[bug-xyz] Thing" "dfritz"
|
check "dfritz exempt — mismatch" 0 "dev/bug-abc/thing" "[bug-xyz] Thing" "dfritz"
|
||||||
@@ -98,6 +114,8 @@ 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 "chore Conventional title" 1 "chore/bump-deps" "chore(deps): bump deps" "dev"
|
||||||
|
|
||||||
# fail: invalid branch forms
|
# fail: invalid branch forms
|
||||||
check "invalid — no role prefix" 1 "feature/foo-bar" "Add feature" "dev"
|
check "invalid — no role prefix" 1 "feature/foo-bar" "Add feature" "dev"
|
||||||
@@ -126,6 +144,12 @@ check_contract "hidden provenance does not satisfy the visible contract" \
|
|||||||
check_contract "comment removal cannot synthesize section headings" \
|
check_contract "comment removal cannot synthesize section headings" \
|
||||||
$'<!-- hidden -->## Tracking\n- Fixes bug-x7k2m9 — https://agenthub.fritzlab.net/bug-x7k2m9\n\n<!-- hidden -->## Attribution\n- Authored-By: Codex (GPT-5) <noreply@openai.com>' \
|
$'<!-- hidden -->## Tracking\n- Fixes bug-x7k2m9 — https://agenthub.fritzlab.net/bug-x7k2m9\n\n<!-- hidden -->## Attribution\n- Authored-By: Codex (GPT-5) <noreply@openai.com>' \
|
||||||
"$BASE" "$GOOD_HEAD" "PR body must contain a non-empty ## Tracking section"
|
"$BASE" "$GOOD_HEAD" "PR body must contain a non-empty ## Tracking section"
|
||||||
|
check_contract "four-space indented backticks do not expose comments" \
|
||||||
|
$' ````\n<!--\n## Tracking\n- Fixes bug-x7k2m9 — https://agenthub.fritzlab.net/bug-x7k2m9\n\n## Attribution\n- Authored-By: Codex (GPT-5) <noreply@openai.com>\n-->' \
|
||||||
|
"$BASE" "$GOOD_HEAD" "PR body must contain a non-empty ## Tracking section"
|
||||||
|
check_contract "unequal backtick runs do not expose comments" \
|
||||||
|
$'`<!--``\n## Tracking\n- Fixes bug-x7k2m9 — https://agenthub.fritzlab.net/bug-x7k2m9\n\n## Attribution\n- Authored-By: Codex (GPT-5) <noreply@openai.com>\n-->' \
|
||||||
|
"$BASE" "$GOOD_HEAD" "PR body must contain a non-empty ## Tracking section"
|
||||||
check_contract_pass "inline code containing comment opener stays visible" \
|
check_contract_pass "inline code containing comment opener stays visible" \
|
||||||
$'Use `<!--` when documenting an HTML comment opener.\n\n## Tracking\n- Fixes bug-x7k2m9 — https://agenthub.fritzlab.net/bug-x7k2m9\n\n## Attribution\n- Authored-By: Codex (GPT-5) <noreply@openai.com>' \
|
$'Use `<!--` when documenting an HTML comment opener.\n\n## Tracking\n- Fixes bug-x7k2m9 — https://agenthub.fritzlab.net/bug-x7k2m9\n\n## Attribution\n- Authored-By: Codex (GPT-5) <noreply@openai.com>' \
|
||||||
"$BASE" "$GOOD_HEAD"
|
"$BASE" "$GOOD_HEAD"
|
||||||
@@ -136,6 +160,8 @@ check_contract_pass "fenced code containing comments stays visible" \
|
|||||||
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"
|
||||||
|
check_chore_contract "chore requires PR attribution" "" "$BASE" "$GOOD_HEAD" "## Attribution must contain"
|
||||||
|
check_chore_contract "chore commits require watermark" "$(canonical_body bug-unused)" "$GOOD_HEAD" "$BAD_HEAD" "must end with an Authored-By"
|
||||||
|
|
||||||
git -C "${FIXTURES}" commit --allow-empty -q -m $'misplaced trailer\nAuthored-By: Codex (GPT-5) <noreply@openai.com>'
|
git -C "${FIXTURES}" commit --allow-empty -q -m $'misplaced trailer\nAuthored-By: Codex (GPT-5) <noreply@openai.com>'
|
||||||
MISPLACED_HEAD=$(git -C "${FIXTURES}" rev-parse HEAD)
|
MISPLACED_HEAD=$(git -C "${FIXTURES}" rev-parse HEAD)
|
||||||
|
|||||||
Reference in New Issue
Block a user
Blocker: You submit
<details title=" open ">with canonical Tracking and Attribution inside. Gitea preserves the attribute and the browser keeps the disclosure collapsed, but this substring match treats the title value as the booleanopenattribute. Exact head returnscheck-naming: ok.The checked
<details open>path has an uncheckedopen-inside-an-attribute-value twin on this line, and it takes the same hidden provenance.titleisn't sanitized away: authenticated/markdownreturned<details title=" open ">. This isn't malformed HTML; it is valid rendered markup and remains collapsed.Parse attributes as tokens, then add this exact reproduction.
Blocker: this regex matches
openanywhere in the serialized tag, including a quoted attribute value.<details title=" open ">has no booleanopenattribute, so the browser keeps its provenance collapsed; Gitea preserves that tag and exact-head execution accepts the hidden Tracking and Attribution. Recognize an actual attribute name boundary outside quoted values, then cover this exact body.Blocker:
openis searched across the whole serialized tag, so an ordinary attribute value containing whitespace-delimitedopenmakes a collapsed disclosure visible to this filter. Gitea preserves<details title="x open y">without the boolean attribute; putting canonical Tracking and Attribution inside it renders collapsed, but exact head exits 0 withcheck-naming: ok. Match the actual boolean attribute without accepting text inside quoted attribute values, then add this reproduction.