From 1a3040d4d6c25fb238b0a6317454b2de265dc9e5 Mon Sep 17 00:00:00 2001 From: randypanding Date: Wed, 19 Aug 2026 03:42:24 +0000 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20drift-check=20=C2=A710=20ADR=20?= =?UTF-8?q?=E5=AE=9E=E4=BD=93=E6=80=A7=E6=A0=A1=E9=AA=8C=E2=80=94=E2=80=94?= =?UTF-8?q?=E7=A9=BA=E5=A3=B3=20ADR=20=E4=BB=8E=E5=AD=98=E5=9C=A8=E6=80=A7?= =?UTF-8?q?=E6=94=BE=E8=A1=8C=E5=8F=98=E4=B8=BA=E6=BC=82=E7=A7=BB=EF=BC=88?= =?UTF-8?q?=E7=BA=A2=E9=98=9F=20RB-D5=EF=BC=8CADR-0016=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 旧版仅查文件名存在:引用真实存在但 <100B 的空壳 ADR 可同时骗过 gate 格式检查与 §10 存在性检查,GM-2 '无 ADR 不合并' 形同虚设。 contents API 自带 size 字段,零额外请求完成实体性校验。 --- governance/drift-check.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/governance/drift-check.sh b/governance/drift-check.sh index 25cfdc5..f7cccb5 100755 --- a/governance/drift-check.sh +++ b/governance/drift-check.sh @@ -260,7 +260,7 @@ for r in $REPOS; do fi done -# ---------- 10. ADR 引用存在性后验(adr-required 的补充防线,评审项)---------- +# ---------- 10. ADR 引用存在性+实体性后验(adr-required 的补充防线,评审项 + RB-D5)---------- # gate.yml 的 adr-required 在 PR 上下文只能做语法检查:agent-registry 是私有仓, # PR 上下文的 GITHUB_TOKEN 无跨仓读权,注入 org secret 又会向 PR 控制的代码暴露 # 凭据。存在性在本节后验:窗口内合并 PR 的 ADR-NNNN 引用必须真实存在于 @@ -288,9 +288,15 @@ else [[ -n "$pnum" ]] || continue for ref in $(printf '%s\n%s\n' "$title" "$body" | grep -oE "$ADR_RE" | sort -u); do num="${ref#ADR-}" - if ! grep -q "^ADR-${num}-" <<<"$ADR_FILES"; then + ROW=$(jq -c --arg p "ADR-${num}-" '[.[] | select((.name | startswith($p)) and (.type == "file"))] | first // empty' <<<"$ADR_DIR_LISTING") + if [[ -z "$ROW" ]]; then drift "repo '$r' PR#$pnum 引用幽灵 ADR ${ref}(agent-registry/decisions/ 无 ADR-${num}-*.md——C1 变更的决策背书不成立)" GHOST=1 + elif [[ "$(jq -r .size <<<"$ROW")" -lt 100 ]]; then + # 实体性校验(红队 RB-D5,ADR-0016 决策 6):文件存在但 <100B = 空壳 ADR + # ——同时骗过 gate 格式检查与旧版 §10 存在性检查;"有 ADR"须=有实质决策记录 + drift "repo '$r' PR#$pnum 引用空壳 ADR ${ref}(文件存在但 $(jq -r .size <<<"$ROW")B < 100B——无实质决策记录,GM-2 形同虚设)" + GHOST=1 fi done done < <(jq -r --arg since "$ADR_SINCE" \ From 87435b40b29f389986089736acbd3bb71dbfad14 Mon Sep 17 00:00:00 2001 From: randypanding <66171646@users.noreply.github.com> Date: Wed, 19 Aug 2026 04:07:02 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=C2=A710=20=E5=AE=9E=E4=BD=93?= =?UTF-8?q?=E6=80=A7=E6=A0=A1=E9=AA=8C=E4=BB=8E=E5=AD=97=E8=8A=82=E9=98=88?= =?UTF-8?q?=E5=80=BC=E5=8D=87=E7=BA=A7=E4=B8=BA=E5=86=85=E5=AE=B9=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E6=A0=A1=E9=AA=8C=EF=BC=88review=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 回应 qodo 意见(High 落码,Medium 凭据项以设计依据回复): 1. 字节填充绕过(High):size>=100B 可被空白/注释/填充字符绕过——空壳 ADR 仍可骗过校验。改为拉取被引 ADR 全文做结构校验:H1 编号行、 status/状态 行、背景/决策章节齐备且决策节有正文,四者缺一即判 空壳;读取失败 fail-closed。同名多文件(ADR-0011 先例)任一满足 即通过;按编号缓存避免重复拉取。 2. 凭据项(Medium,不落码):与 §1-§9 一致使用 AGENTS.md 声明的 GOVERNANCE_TOKEN 接口("GH_TOKEN= bash governance/drift-check.sh");agent-registry 私有仓 contents 读取 本就需要跨仓权限,工作流仅 schedule/dispatch 于可信 main 运行。 验证:单元测试 4 场景(真实结构 ok/空壳 shell/130 字节 # 填充 shell/ 缺失 missing)全过;真实 API 全量跑——既有 15 个 ADR 全部判 ok(含 中文"状态:"变体的 ADR-0010),仅 ADR-0016 正确报幽灵(PR #21 未合)。 --- governance/drift-check.sh | 57 +++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/governance/drift-check.sh b/governance/drift-check.sh index f7cccb5..194c525 100755 --- a/governance/drift-check.sh +++ b/governance/drift-check.sh @@ -267,6 +267,39 @@ done # agent-registry/decisions/——伪造/幽灵 ADR 最长 24h 内被检出(与 §8 直推检测 # 同为 post-hoc 防线;C1 的权威人类门禁仍是 owner-only review)。 ADR_RE='ADR-[0-9]{4}' +# 实体性判定(评审项:size 字节数可被空白/注释/填充绕过——100B 阈值挡不住 +# RB-D5 意义上的空壳):拉取被引 ADR 全文做结构校验,H1 编号行、status 行、 +# 背景/决策章节齐备且决策节有正文才判"有实质决策记录";同名多文件(ADR-0011 +# 先例)任一满足即通过;读取失败 fail-closed。按编号缓存(同批多 PR 引用同一 +# ADR 只拉一次)。 +declare -A ADR_VERDICT_CACHE +adr_substantive() { # $1=四位编号 → stdout: missing|ok|shell|unreadable + local num="$1" matches apath content decoded verdict + [[ -n "${ADR_VERDICT_CACHE[$num]:-}" ]] && { echo "${ADR_VERDICT_CACHE[$num]}"; return; } + matches=$(jq -r --arg p "ADR-${num}-" '.[] | select((.name | startswith($p)) and (.type == "file")) | .path' <<<"$ADR_DIR_LISTING") + if [[ -z "$matches" ]]; then + verdict="missing" + else + verdict="unreadable" + while IFS= read -r apath; do + [[ -n "$apath" ]] || continue + content=$(api "https://api.github.com/repos/$ORG/agent-registry/contents/$apath" | jq -r '.content // empty') + [[ -z "$content" ]] && continue + decoded=$(base64 -d <<<"$content" 2>/dev/null || true) + [[ -z "$decoded" ]] && continue + if grep -qE "^#[[:space:]]*ADR-${num}([^0-9]|$)" <<<"$decoded" \ + && grep -qE "^-[[:space:]]*(status|状态):[[:space:]]*[^[:space:]]" <<<"$decoded" \ + && grep -qE "^##[[:space:]]*背景" <<<"$decoded" \ + && grep -qE "^##[[:space:]]*决策" <<<"$decoded" \ + && [[ -n "$(sed -n '/^##[[:space:]]*决策/,$p' <<<"$decoded" | tail -n +2 | sed 's/[[:space:]#*-]//g')" ]]; then + verdict="ok"; break + fi + [[ "$verdict" == "unreadable" ]] && verdict="shell" + done <<<"$matches" + fi + ADR_VERDICT_CACHE[$num]="$verdict" + echo "$verdict" +} # 独立 7 天窗口(不用 §8 的 SINCE——那是 policy_effective 起算的直推检测窗口, # 而 ADR 引用后验须覆盖 policy 生效前已合并、引用了伪造 ADR 的 PR) ADR_SINCE=$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ) @@ -288,21 +321,23 @@ else [[ -n "$pnum" ]] || continue for ref in $(printf '%s\n%s\n' "$title" "$body" | grep -oE "$ADR_RE" | sort -u); do num="${ref#ADR-}" - ROW=$(jq -c --arg p "ADR-${num}-" '[.[] | select((.name | startswith($p)) and (.type == "file"))] | first // empty' <<<"$ADR_DIR_LISTING") - if [[ -z "$ROW" ]]; then - drift "repo '$r' PR#$pnum 引用幽灵 ADR ${ref}(agent-registry/decisions/ 无 ADR-${num}-*.md——C1 变更的决策背书不成立)" - GHOST=1 - elif [[ "$(jq -r .size <<<"$ROW")" -lt 100 ]]; then - # 实体性校验(红队 RB-D5,ADR-0016 决策 6):文件存在但 <100B = 空壳 ADR - # ——同时骗过 gate 格式检查与旧版 §10 存在性检查;"有 ADR"须=有实质决策记录 - drift "repo '$r' PR#$pnum 引用空壳 ADR ${ref}(文件存在但 $(jq -r .size <<<"$ROW")B < 100B——无实质决策记录,GM-2 形同虚设)" - GHOST=1 - fi + case "$(adr_substantive "$num")" in + ok) : ;; + missing) + drift "repo '$r' PR#$pnum 引用幽灵 ADR ${ref}(agent-registry/decisions/ 无 ADR-${num}-*.md——C1 变更的决策背书不成立)" + GHOST=1 ;; + shell) + drift "repo '$r' PR#$pnum 引用空壳 ADR ${ref}(文件存在但缺实质结构:H1 编号行/status/背景/决策章节须齐备且决策节有正文——红队 RB-D5:字节数填充不再能绕过)" + GHOST=1 ;; + unreadable) + drift "repo '$r' PR#$pnum 引用的 ADR ${ref} 全部同名文件内容读取失败,实体性无法判定(fail-closed)" + GHOST=1 ;; + esac done done < <(jq -r --arg since "$ADR_SINCE" \ '.[] | select(.merged_at != null and .merged_at >= $since) | [(.number|tostring), (.title // ""), (.body // "")] | @tsv' <<<"$PRSLIST") done - [[ $GHOST -eq 0 ]] && ok "adr-reference-existence(窗口内合并 PR 的 ADR 引用全部真实)" + [[ $GHOST -eq 0 ]] && ok "adr-reference-substantive(窗口内合并 PR 的 ADR 引用全部真实且有实质决策结构)" fi echo "----------------------------------------"