-
Notifications
You must be signed in to change notification settings - Fork 0
governance: required check 活体验证 + PR liveness 侦测(P1-4,ADR-0034) #108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -439,6 +439,100 @@ else | |||||||||||||||||||||||||||||||||
| [[ $REQ_MISSING -eq 0 ]] && ok "CI-Workflows 必需大版本指针存在($CW_REQUIRED_POINTERS)" | ||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # ---------- 12. required check 活体存在性(P1-4,ADR-0034)---------- | ||||||||||||||||||||||||||||||||||
| # 文本对账 ≠ 生效验证:ruleset JSON 完全正确的同时,required check 字符串精确 | ||||||||||||||||||||||||||||||||||
| # 匹配可能实际为空(job 改名 / workflow 重构)→ "零 required check" → PR 裸奔。 | ||||||||||||||||||||||||||||||||||
| # 每个受管仓最近活动的 PR head(无 PR 活动时退化为默认分支 HEAD)上,必须存在 | ||||||||||||||||||||||||||||||||||
| # 每个 required check 名(从 rulesets/*.json 派生——单一真源)的 check run 且 | ||||||||||||||||||||||||||||||||||
| # conclusion 非空。fail-closed:check-runs 查询失败即判漂移,不用部分结果。 | ||||||||||||||||||||||||||||||||||
| REQ_CHECKS=$(jq -rs '[.[].rules[]? | select(.type == "required_status_checks") | ||||||||||||||||||||||||||||||||||
| | .parameters.required_status_checks[].context] | unique | .[]' "$DIR"/rulesets/*.json) | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+448
to
+449
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 9. Check names split on spaces Required contexts are extracted as newline-delimited strings but iterated with unquoted shell word splitting. Any valid check name containing whitespace is broken into multiple names and falsely reported missing. Agent Prompt
|
||||||||||||||||||||||||||||||||||
| [[ -n "$REQ_CHECKS" ]] || { echo "FATAL: rulesets 未声明任何 required check——§12 活体验证失去判据"; exit 2; } | ||||||||||||||||||||||||||||||||||
| epoch_of() { date -u -d "$1" +%s; } # ISO8601 → epoch(runner GNU date) | ||||||||||||||||||||||||||||||||||
| for r in $REPOS; do | ||||||||||||||||||||||||||||||||||
| jq -e --arg r "$r" '($r as $x | . | index($x)) != null' <<<"$EXCLUDES" >/dev/null && continue | ||||||||||||||||||||||||||||||||||
| # 候选 head:最近更新的至多 3 个 PR head sha;无 PR 活动则退化为默认分支 HEAD | ||||||||||||||||||||||||||||||||||
| PRS_RECENT=$(api "https://api.github.com/repos/$ORG/$r/pulls?state=all&sort=updated&direction=desc&per_page=20") | ||||||||||||||||||||||||||||||||||
| if jq -e 'type == "array"' <<<"$PRS_RECENT" >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||
| HEADS=$(jq -r '[.[] | .head.sha][0:3][]' <<<"$PRS_RECENT") | ||||||||||||||||||||||||||||||||||
| [[ -n "$HEADS" ]] || HEADS=$(api "https://api.github.com/repos/$ORG/$r/git/ref/heads/main" | jq -r '.object.sha // empty') | ||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||
| drift "repo '$r' PR 清单拉取失败,required check 活体验证无法执行(fail-closed)" | ||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||
| [[ -n "$HEADS" ]] || { drift "repo '$r' 无 PR 活动且默认分支 HEAD 不可读,活体验证无载体(fail-closed)"; continue; } | ||||||||||||||||||||||||||||||||||
| LIVE_MISS=0; QUERY_FAIL=0 | ||||||||||||||||||||||||||||||||||
| for ctx in $REQ_CHECKS; do | ||||||||||||||||||||||||||||||||||
| FOUND=0 | ||||||||||||||||||||||||||||||||||
| while IFS= read -r sha; do | ||||||||||||||||||||||||||||||||||
| [[ -n "$sha" ]] || continue | ||||||||||||||||||||||||||||||||||
| CRS=$(api "https://api.github.com/repos/$ORG/$r/commits/$sha/check-runs?per_page=100") | ||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1. governance_token bypasses app identity The new cross-repository pull-request and check-run queries use the generic organization-wide GOVERNANCE_TOKEN instead of tokens obtained through scripts/gh-app-token.sh. This bypasses the required cloudbrid-agent identity and single-repository token scope. Agent Prompt
|
||||||||||||||||||||||||||||||||||
| if ! jq -e 'type == "object" and has("check_runs")' <<<"$CRS" >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+469
to
+470
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 6. Check runs omit later pages Both new sections request only the first 100 check runs, so a required or stale run on a later page is invisible. §12 can consequently report false missing drift, while §13 can report a PR healthy despite an old pending run. Agent Prompt
|
||||||||||||||||||||||||||||||||||
| QUERY_FAIL=1; continue | ||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||
| if jq -e --arg c "$ctx" '[.check_runs[] | select(.name == $c and .conclusion != null)] | length > 0' <<<"$CRS" >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+470
to
+473
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 4. Partial failures pass green A failed check-runs query only produces drift when no later head contains the required context. If another candidate succeeds, §12 exits green despite being unable to validate one candidate, contradicting its fail-closed contract. Agent Prompt
|
||||||||||||||||||||||||||||||||||
| FOUND=1; break | ||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+473
to
+475
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3. Older pr masks missing check §12 marks a required context live when it exists on any of the three selected PR heads, so completed checks from older PRs mask its absence on the newest PR. A workflow job rename therefore remains undetected until all three candidates no longer contain the old check name, defeating the intended immediate liveness validation. Agent Prompt
|
||||||||||||||||||||||||||||||||||
| done <<<"$HEADS" | ||||||||||||||||||||||||||||||||||
| if [[ $QUERY_FAIL -eq 1 && $FOUND -eq 0 ]]; then | ||||||||||||||||||||||||||||||||||
| drift "repo '$r' check-runs 查询失败,required check '$ctx' 活体无法验证(fail-closed)" | ||||||||||||||||||||||||||||||||||
| elif [[ $FOUND -ne 1 ]]; then | ||||||||||||||||||||||||||||||||||
| drift "repo '$r' required check '$ctx' 活体缺失:ruleset 文本正确但最近 PR head / main HEAD 均无该 check run——job 改名或 workflow 重构?裸奔窗口已开启(ADR-0034 §12)" | ||||||||||||||||||||||||||||||||||
| LIVE_MISS=1 | ||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||
| [[ $LIVE_MISS -eq 0 && $QUERY_FAIL -eq 0 ]] && ok "required-check-live '$r'(${HEADS//$'\n'/ } 上 ${REQ_CHECKS//$'\n'/ } 齐备)" | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+477
to
+484
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 查询失败时始终记录漂移。 当一个 head 的 check-runs 查询失败、另一个 head 找到同名 check 时, - if [[ $QUERY_FAIL -eq 1 && $FOUND -eq 0 ]]; then
+ if [[ $QUERY_FAIL -eq 1 ]]; then
drift "repo '$r' check-runs 查询失败,required check '$ctx' 活体无法验证(fail-closed)"📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # ---------- 13. PR liveness 侦测(P1-4,ADR-0034)---------- | ||||||||||||||||||||||||||||||||||
| # 治理不漂移但流水线死了的三类形态(#81 §6——无人值守下卡死 PR 是最隐形的 | ||||||||||||||||||||||||||||||||||
| # 人类瓶颈):(a) auto-merge 已设置但 > 阈值无进展;(b) check 停留 queued/ | ||||||||||||||||||||||||||||||||||
| # in_progress 超 > 阈值;(c) PR 创建超阈值且 head 上零 check run(应有而无)。 | ||||||||||||||||||||||||||||||||||
| # 命中即走 GM-1 既有漂移 issue 通道。阈值:expected-state pr_liveness_hours, | ||||||||||||||||||||||||||||||||||
| # 环境变量 PR_LIVENESS_HOURS 可覆盖(dispatch input liveness_hours 透传,注入测试用)。 | ||||||||||||||||||||||||||||||||||
| LIVENESS_H="${PR_LIVENESS_HOURS:-$(jq -r '.pr_liveness_hours // 4' "$EXPECTED")}" | ||||||||||||||||||||||||||||||||||
| LIVENESS_S=$(( LIVENESS_H * 3600 )) | ||||||||||||||||||||||||||||||||||
| STUCK_TOTAL=0 | ||||||||||||||||||||||||||||||||||
| for r in $REPOS; do | ||||||||||||||||||||||||||||||||||
| jq -e --arg r "$r" '($r as $x | . | index($x)) != null' <<<"$EXCLUDES" >/dev/null && continue | ||||||||||||||||||||||||||||||||||
| OPEN_PRS=$(api "https://api.github.com/repos/$ORG/$r/pulls?state=open&per_page=30") | ||||||||||||||||||||||||||||||||||
| jq -e 'type == "array"' <<<"$OPEN_PRS" >/dev/null 2>&1 \ | ||||||||||||||||||||||||||||||||||
| || { drift "repo '$r' open PR 清单拉取失败,liveness 侦测无法执行(fail-closed)"; continue; } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+498
to
+500
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 7. Open prs truncated §13 examines only the first 30 open PRs in each repository and still reports the repository healthy. Repositories with more than 30 open PRs can therefore have stuck PRs that are never inspected. Agent Prompt
|
||||||||||||||||||||||||||||||||||
| STUCK=0 | ||||||||||||||||||||||||||||||||||
| while IFS=$'\t' read -r pnum created updated headsha has_am; do | ||||||||||||||||||||||||||||||||||
| [[ -n "$pnum" ]] || continue | ||||||||||||||||||||||||||||||||||
| AGE_UPD=$(( NOW_EPOCH - $(epoch_of "$updated") )) | ||||||||||||||||||||||||||||||||||
| AGE_CRE=$(( NOW_EPOCH - $(epoch_of "$created") )) | ||||||||||||||||||||||||||||||||||
| if [[ "$has_am" == "true" && $AGE_UPD -gt $LIVENESS_S ]]; then | ||||||||||||||||||||||||||||||||||
| drift "repo '$r' PR#$pnum auto-merge 已开启但 ${LIVENESS_H}h 无进展(updated ${AGE_UPD}s 前)——卡死侦测 (a):查 required check 状态/分支冲突(ADR-0034 §13)" | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+506
to
+507
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 8. Dynamic ages defeat deduplication The new drift messages embed continuously changing age values, but the workflow fingerprint normalization only replaces the older 回填时限= format. A persistent stuck PR therefore gets a new fingerprint and duplicate issue comment every hourly run. Agent Prompt
|
||||||||||||||||||||||||||||||||||
| STUCK=1; continue | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+506
to
+508
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2. drift uses forbidden issue channel The new PR-liveness alerts call drift, causing the workflow to publish machine feedback through repository issues. The automation standard permits only failed check runs or ordinary PR comments, so the configured issue-reporting channel is noncompliant for these new alerts. Agent Prompt
|
||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||
| CRS=$(api "https://api.github.com/repos/$ORG/$r/commits/$headsha/check-runs?per_page=100") | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+498
to
+510
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 2 'pulls\?state=open.*per_page|check-runs\?per_page|[?&]page=' governance/drift-check.shRepository: Cloudbird-Software/.github Length of output: 2267 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- API helper and required-check liveness ---'
sed -n '1,120p' governance/drift-check.sh
sed -n '430,535p' governance/drift-check.sh
printf '%s\n' '--- Relevant endpoint occurrences ---'
rg -n -C 3 'pulls\?state=open|check-runs\?per_page|while .*PAGE|page=' governance/drift-check.shRepository: Cloudbird-Software/.github Length of output: 16032 分页读取所有开放 PR 和 check runs。
为相关 API 请求增加分页循环,并在分页失败时保持 fail-closed 行为。 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| if ! jq -e 'type == "object" and has("check_runs")' <<<"$CRS" >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||
| drift "repo '$r' PR#$pnum head check-runs 查询失败,liveness 无法验证(fail-closed)" | ||||||||||||||||||||||||||||||||||
| STUCK=1; continue | ||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||
| N_RUNS=$(jq '.check_runs | length' <<<"$CRS") | ||||||||||||||||||||||||||||||||||
| if [[ "$N_RUNS" -eq 0 && $AGE_CRE -gt $LIVENESS_S ]]; then | ||||||||||||||||||||||||||||||||||
| drift "repo '$r' PR#$pnum 创建 ${AGE_CRE}s 且 head 零 check run——卡死侦测 (c):应有而无(workflow 未触发/被改名,ADR-0034 §13)" | ||||||||||||||||||||||||||||||||||
| STUCK=1; continue | ||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||
| # pending 超龄判定(b):shell 循环做日期运算(jq 无日期运算) | ||||||||||||||||||||||||||||||||||
| while IFS=$'\t' read -r crname crstatus crstart; do | ||||||||||||||||||||||||||||||||||
| [[ -n "$crname" ]] || continue | ||||||||||||||||||||||||||||||||||
| [[ "$crstatus" == "queued" || "$crstatus" == "in_progress" ]] || continue | ||||||||||||||||||||||||||||||||||
| [[ -n "$crstart" ]] || continue | ||||||||||||||||||||||||||||||||||
| AGE_PEND=$(( NOW_EPOCH - $(epoch_of "$crstart") )) | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+523
to
+525
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 5. Queued checks evade aging §13 skips pending runs whose started_at is null, even though it explicitly includes queued runs and the GitHub response schema permits a null start time. Such a run can remain queued indefinitely without triggering liveness condition (b). Agent Prompt
|
||||||||||||||||||||||||||||||||||
| if [[ $AGE_PEND -gt $LIVENESS_S ]]; then | ||||||||||||||||||||||||||||||||||
| drift "repo '$r' PR#$pnum check '$crname' 停留 $crstatus 已 ${AGE_PEND}s——卡死侦测 (b):永久 pending(ADR-0034 §13)" | ||||||||||||||||||||||||||||||||||
| STUCK=1 | ||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||
| done < <(jq -r '.check_runs[] | [.name, .status, (.started_at // "")] | @tsv' <<<"$CRS") | ||||||||||||||||||||||||||||||||||
| done < <(jq -r '.[] | [(.number|tostring), .created_at, .updated_at, .head.sha, (.auto_merge != null | tostring)] | @tsv' <<<"$OPEN_PRS") | ||||||||||||||||||||||||||||||||||
| STUCK_TOTAL=$((STUCK_TOTAL+STUCK)) | ||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||
| [[ $STUCK_TOTAL -eq 0 ]] && ok "pr-liveness(全部受管仓 open PR 无卡死,阈值 ${LIVENESS_H}h)" | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| echo "----------------------------------------" | ||||||||||||||||||||||||||||||||||
| if [[ $DRIFTS -gt 0 ]]; then | ||||||||||||||||||||||||||||||||||
| echo "结果: $DRIFTS 项漂移。修复: bash governance/apply.sh 或手动改回" | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: Cloudbird-Software/.github
Length of output: 289
🏁 Script executed:
Repository: Cloudbird-Software/.github
Length of output: 10244
🏁 Script executed:
Repository: Cloudbird-Software/.github
Length of output: 8382
🏁 Script executed:
Repository: Cloudbird-Software/.github
Length of output: 198
🏁 Script executed:
Repository: Cloudbird-Software/.github
Length of output: 6237
严重:在算术扩展前校验
PR_LIVENESS_HOURS。攻击者可将值设为
DRIFTS[$(…)]。DRIFTS已在脚本中定义,因此该命令替换会在LIVENESS_S=$(( LIVENESS_H * 3600 ))中执行。作业环境包含GH_TOKEN。先拒绝非正十进制整数,再使用
10#$LIVENESS_H计算秒数。🤖 Prompt for AI Agents