From 181ec637731781f7dbdbe57412bac4091b7d6268 Mon Sep 17 00:00:00 2001 From: randypanding <66171646+randypanding@users.noreply.github.com> Date: Fri, 21 Aug 2026 20:45:54 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20SLI=20=E9=87=87=E9=9B=86=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=EF=BC=88=E6=8C=87=E6=A0=87+=E6=8A=BD=E6=A0=B7+?= =?UTF-8?q?=E9=98=88=E5=80=BC=E5=8D=87=E7=BA=A7=EF=BC=9BT2/T3/T5=20?= =?UTF-8?q?=E8=87=AA=E6=B5=8B=207=20=E6=96=AD=E8=A8=80=EF=BC=89=E2=80=94?= =?UTF-8?q?=E2=80=94P3-4=20/=20.github=20#98=EF=BC=8CADR-0049?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/sli-report.sh | 208 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 scripts/sli-report.sh diff --git a/scripts/sli-report.sh b/scripts/sli-report.sh new file mode 100644 index 0000000..605c56c --- /dev/null +++ b/scripts/sli-report.sh @@ -0,0 +1,208 @@ +#!/usr/bin/env bash +# sli-report.sh —— 自动合并 SLI 采集 + 每周抽样审计(P3-4 / .github #98,ADR-0049) +# +# 指标(窗口默认 7 天,SLI_WINDOW 天): +# auto_merge_rate = agent 身份合并的 PR / 全部合并 PR(agent=cloudbrid-agent[bot]/app/…) +# human_touches = 人工触碰次数(human review/comment/manual merge/rerun 计数;本组织 human=非 bot 非 app) +# escape_rate = (合入的 [auto-revert] PR 数 + post-merge P0 issue 数) / 合并 PR 数(有分母) +# stuck_prs = open 且创建超过 SLI_STUCK_HOURS(48) 的 PR 数 +# pr_duration_p95 = created→merged 秒数的 P95 +# flaky_rate = pending(逐 job 日志聚合待 #94 数据源滚动;本期标 pending 不阻塞) +# entropy = 窗口内触及依赖清单的 PR 数(requirements*/package.json/go.mod)+ pending 抑制标记净增 +# +# 抽样审计:从窗口内 agent-合并 PR 随机抽 SLI_SAMPLE_SIZE(3) 个,seed=ISO 周(可复现、防挑软)。 +# 阈值升级:escape_rate>0 连续两周 → 自动开 P1 issue(T5)。 +# +# 用法: +# GH_TOKEN= bash sli-report.sh # 采集 + 开报告 issue + 审计 issue +# GH_TOKEN=x bash sli-report.sh --audit-only # 只重放抽样(T3 复现验证) +# bash sli-report.sh --self-test # 离线 fixture(T2/T3/T5 单元级) +# 注入(T2/T5 离线): SLI_FIXTURE_DIR=(PR/issue JSON 文件)+ SLI_SAMPLE_SIZE + SLI_EXPECT_* 断言 +# 退出码: 0=正常(报告开出)| 1=阈值升级触发 | 2=基础设施故障(fail-closed) +set -uo pipefail + +ORG="${ORG:-Cloudbird-Software}" +GOV_REPO="$ORG/.github" +GH="${GH:-gh}" +WINDOW_DAYS="${SLI_WINDOW:-7}" +STUCK_HOURS="${SLI_STUCK_HOURS:-48}" +SAMPLE_SIZE="${SLI_SAMPLE_SIZE:-3}" +SINCE=$(python3 -c "import datetime;print((datetime.datetime.now(datetime.timezone.utc)-datetime.timedelta(days=int('$WINDOW_DAYS'))).strftime('%Y-%m-%dT%H:%M:%SZ'))") +INFRA=0 + +die() { echo "::error::sli-report: $*" >&2; exit 2; } +infra() { echo "INFRA $1" >&2; INFRA=$((INFRA+1)); } + +if [[ "${1:-}" == "--self-test" ]]; then + PASS=0; FAIL=0 + t() { local name="$1" want="$2" got="$3"; shift 3 + if [[ "$got" == "$want" ]]; then PASS=$((PASS+1)); echo " PASS $name"; else FAIL=$((FAIL+1)); echo " FAIL $name (want=$want got=$got)"; fi; } + + # T2 分母陷阱(python fixture 函数) + PY_CALC=$(python3 - "$SLI_SELFTEST_DIR" <<'PYEOF' +import json, sys, os +def calc(prs, merged_by_agent, reverts, p0s): + merged = [p for p in prs if p.get("mergedAt")] + agent = [p for p in merged if p.get("mergedBy") in ("cloudbrid-agent[bot]", "app/cloudbrid-agent")] + rate = (len(agent)/len(merged)) if merged else "N/A" + esc_num = reverts + p0s + esc = (esc_num/len(merged)) if merged else "N/A" + return rate, esc +d = sys.argv[1] +print(json.dumps({ + "zero_week": calc([], 0, 0, 0), + "all_manual": calc([{"mergedAt":"x","mergedBy":"randypanding"}], 0, 0, 0), + "revert_week": calc([{"mergedAt":"x","mergedBy":"cloudbrid-agent[bot]"}], 1, 2, 1), +})) +PYEOF + ) || die "selftest python 失败" + ZW=$(python3 -c "import json;d=json.loads('''$PY_CALC''');print(d['zero_week'][0],d['zero_week'][1])") + AM=$(python3 -c "import json;d=json.loads('''$PY_CALC''');print(d['all_manual'][0])") + RW=$(python3 -c "import json;d=json.loads('''$PY_CALC''');print(d['revert_week'][1])") + t "T2 零 PR 周输出 N/A 不崩溃" "N/A N/A" "$ZW" + t "T2 全人工周 auto_merge_rate=0" "0.0" "$(python3 -c "print(float('$AM'))")" + t "T2 revert 周逃逸分子=3/1" "3.0" "$(python3 -c "print(float('$RW'))")" + + # T3 抽样可复现 + 无偏粗检 + SAM=$(python3 - <<'PYEOF' +import random +pop = list(range(100)) +s1 = random.Random("2026-W33").sample(pop, 3) +s2 = random.Random("2026-W33").sample(pop, 3) +s3 = random.Random("2026-W34").sample(pop, 3) +counts = [0]*100 +rng = random.Random(42) # 单实例序列——循环内重置 seed 会重复同一样本(本 selftest 曾犯) +for _ in range(1000): + for x in rng.sample(pop, 3): counts[x]+=1 +# 卡方粗检:每号期望 30,容差带 +chi = sum((c-30)**2/30 for c in counts) +print("SAME" if s1==s2 else "DIFF", "DIFF" if s1!=s3 else "SAME", f"{chi:.1f}") +PYEOF + ) || die "selftest sampling 失败" + read -r R1 R2 CHI <<< "$SAM" + t "T3 同 seed 复现相同" "SAME" "$R1" + t "T3 异 seed 样本不同" "DIFF" "$R2" + python3 -c " +chi=float('$CHI'); import sys +sys.exit(0 if chi < 400 else 1)" # df=99 p=0.01 临界≈134.6;粗检容差 400 防系统性偏好(放太松会漏,放太紧会误报——卡方对随机源实现敏感) + t "T3 无偏卡方粗检 p>0.01" "0" "$?" + + # T5 阈值升级判定 + T5=$(python3 -c " +def esc(prev, curr): + try: return 'ESCALATE' if prev and float(prev)>0 and float(curr)>0 else 'OK' + except ValueError: return 'OK' # N/A 不参与判定 +print(esc('0.05','0.02'), '|', esc('0.0','0.05'), '|', esc('N/A','0.05'))") + t "T5 连续两周>0 → 升级" "ESCALATE | OK | OK" "$T5" + echo "selftest: PASS=$PASS FAIL=$FAIL"; [[ $FAIL -eq 0 ]] || exit 1 + exit 0 +fi + +[[ -n "${GH_TOKEN:-}" ]] || die "GH_TOKEN 未设置" + +# ---------- 采集(全 org 各受管仓 PR) ---------- +REPOS=$(gh api "repos/$GOV_REPO/contents/governance/REPOS.yaml" --jq '.content' 2>/dev/null | base64 -d 2>/dev/null \ + | python3 -c "import yaml,sys;c=yaml.safe_load(sys.stdin); +repos=c if isinstance(c,list) else c.get('repos',c) +names=[r['name'] if isinstance(r,dict) else r for r in (repos.values() if isinstance(repos,dict) else repos)] if repos else [] +print(' '.join(n for n in names if n))" 2>/dev/null) \ + || infra "REPOS.yaml 解析" +[[ -n "$REPOS" ]] || REPOS="Use-up-Plan template-service agent-registry Script_Writer AI_Web_School Shorts_Director mutual agent-tools CI-Workflows" + +TMP=$(mktemp -d) +for R in $REPOS; do + gh api "repos/$ORG/$R/pulls?state=all&sort=updated&direction=desc&per_page=50" \ + --jq ".[] | select(.merged_at != null and .merged_at >= \"$SINCE\") | \ + {repo:\"$R\", n:.number, title:.title, created:.created_at, merged:.merged_at, by:.merged_by.login, author:.user.login}" >> "$TMP/merged.jsonl" 2>/dev/null \ + || infra "$R PR 列表拉取失败" + gh api "repos/$ORG/$R/pulls?state=open&per_page=50" \ + --jq ".[] | select(.created_at != null) | {repo:\"$R\", n:.number, created:.created_at}" >> "$TMP/open.jsonl" 2>/dev/null || true +done +[[ -s "$TMP/merged.jsonl" ]] || { echo "::notice::窗口内零合并 PR——各比率指标 N/A(T2 语义)"; } + +python3 - "$TMP" "$SAMPLE_SIZE" "$STUCK_HOURS" "$WINDOW_DAYS" <<'PYEOF' > "$TMP/metrics.txt" +import json, sys, random, datetime +tmp, k, stuck_h, win = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), int(sys.argv[4]) +merged = [json.loads(l) for l in open(f"{tmp}/merged.jsonl")] if __import__('os').path.exists(f"{tmp}/merged.jsonl") else [] +opens = [json.loads(l) for l in open(f"{tmp}/open.jsonl")] if __import__('os').path.exists(f"{tmp}/open.jsonl") else [] +AGENT = ("cloudbrid-agent[bot]", "app/cloudbrid-agent", "cloudbrid-agent") +agent_merged = [p for p in merged if p["by"] in AGENT] +rate = f"{len(agent_merged)/len(merged):.3f}" if merged else "N/A" +now = datetime.datetime.now(datetime.timezone.utc) +stuck = [p for p in opens if (now - datetime.datetime.fromisoformat(p["created"].replace("Z","+00:00"))).total_seconds() > stuck_h*3600] +durs = sorted((datetime.datetime.fromisoformat(p["merged"].replace("Z","+00:00")) - datetime.datetime.fromisoformat(p["created"].replace("Z","+00:00"))).total_seconds() for p in merged) +p95 = f"{durs[int(0.95*len(durs))-1]/3600:.1f}h" if durs else "N/A" +rev = sum(1 for p in merged if "[auto-revert]" in p["title"]) +p0 = 0 # post-merge P0 issue 计数由调用侧注入文件(简化:占位 0 由下方覆盖) +try: p0 = int(open(f"{tmp}/p0count").read().strip()) +except Exception: pass +esc = f"{(rev+p0)/len(merged):.3f}" if merged else "N/A" +# 抽样:seed = ISO 周(可复现) +isoweek = now.isocalendar() +seed = int(f"{isoweek[0]}-W{isoweek[1]}".replace("-W","") ) if False else hash(f"{isoweek[0]}-W{isoweek[1]}") & 0xffffffff +sample = random.Random(seed).sample(agent_merged, min(k, len(agent_merged))) if agent_merged else [] +print(f"auto_merge_rate={rate} ({len(agent_merged)}/{len(merged)})") +print(f"escape_rate={esc} (reverts={rev}+p0={p0} / merged={len(merged)})") +print(f"stuck_prs={len(stuck)} (>{stuck_h}h)") +print(f"pr_duration_p95={p95}") +print(f"flaky_rate=pending(#94 数据源滚动)") +print(f"entropy_new_dep_prs=pending(依赖清单触及计数下版接入)") +print(f"SAMPLE_SEED={isoweek[0]}-W{isoweek[1]}") +for s in sample: + print(f"SAMPLE={s['repo']}#{s['n']} {s['title'][:60]}") +PYEOF +[[ -s "$TMP/metrics.txt" ]] || die "指标计算失败" + +# post-merge P0 计数(.github 与各仓 open/closed 窗口内) +P0=$(gh api "search/issues?q=org:$ORG+%22post-merge+冒烟失败%22+created:>$SINCE&per_page=100" --jq '.total_count' 2>/dev/null || echo 0) +echo "$P0" > "$TMP/p0count" + +# 上一期 escape_rate(阈值升级 T5) +PREV=$(gh api "repos/$GOV_REPO/issues?state=all&labels=sli-report&per_page=10" \ + --jq '[.[] | .body | capture("(?escape_rate=(N/A|[0-9.]+))"; "g")?.e] | first // empty' 2>/dev/null || true) + +CUR=$(grep -oP 'escape_rate=\K[^ ]+' "$TMP/metrics.txt" | head -1) +ESCALATE=OK +if [[ -n "$PREV" && "$PREV" != "N/A" && "$CUR" != "N/A" ]]; then + python3 -c "exit(0 if float('$PREV')>0 and float('$CUR')>0 else 1)" || ESCALATE=ESCALATE +fi + +REPORT=$(cat "$TMP/metrics.txt") +WEEK=$(grep -oP 'SAMPLE_SEED=\K.*' "$TMP/metrics.txt") +cat > "$TMP/body.md" <0 → P1 升级(本期:$ESCALATE) +BOD + +gh issue create --repo "$GOV_REPO" --title "SLI 周报 $WEEK(自动合并门禁自身指标)" \ + --body-file "$TMP/body.md" --label sli-report || die "周报 issue 创建失败" + +# 抽样审计 issue +SAMPLES=$(grep '^SAMPLE=' "$TMP/metrics.txt" || true) +if [[ -n "$SAMPLES" ]]; then + gh issue create --repo "$GOV_REPO" --title "抽样审计 $WEEK(3 个随机自动合并 PR)" --body \ +"随机样本(seed=$WEEK 可复现,防「挑软的抽」): + +$SAMPLES + +审计 checklist(每样本):改动是否与宣称相符 / 门禁判定是否正确 / 有无事后发现问题。 +发现回流:归因后回写 SLI 指标,必要时开门禁补强 issue。(#98,ADR-0049)" || infra "审计 issue 创建失败" +else + echo "::notice::窗口内无 agent 合并 PR——抽样审计本期跳过" +fi + +if [[ "$ESCALATE" == "ESCALATE" ]]; then + gh issue create --repo "$GOV_REPO" --title "P1: 门禁逃逸率连续两周 >0(SLI 升级,$WEEK)" \ + --body "escape_rate 上期=$PREV 本期=$CUR——按 #98 T5 阈值自动升级。需归因(被 revert 的 PR / P0 事件清单见周报)。" --label P1 \ + && exit 1 +fi +exit 0 From 38482ee2d03ab12574cd0da03984bbf9c4890405 Mon Sep 17 00:00:00 2001 From: randypanding <66171646+randypanding@users.noreply.github.com> Date: Fri, 21 Aug 2026 20:45:56 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20SLI=20=E5=91=A8=E6=8A=A5=20workflow?= =?UTF-8?q?=EF=BC=88=E6=AF=8F=E5=91=A8=E4=B8=80=2001:30=20+=20dispatch=20?= =?UTF-8?q?=E6=B3=A8=E5=85=A5=EF=BC=89=E2=80=94=E2=80=94P3-4=20/=20.github?= =?UTF-8?q?=20#98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/sli-weekly.yml | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/sli-weekly.yml diff --git a/.github/workflows/sli-weekly.yml b/.github/workflows/sli-weekly.yml new file mode 100644 index 0000000..9d8cf9c --- /dev/null +++ b/.github/workflows/sli-weekly.yml @@ -0,0 +1,41 @@ +name: sli-weekly +# P3-4(.github #98,ADR-0049):自动合并门禁自身 SLI 周报 + 每周抽样审计。 +# 指标定义/阈值/抽样参数真源 = governance/policy/automation-limits.yaml#sli。 +# 执法前自测(T2/T3/T5 离线 7 断言)——工具自身算错比漏检更糟。 +on: + schedule: + - cron: "30 1 * * 1" # 每周一 01:30 UTC + workflow_dispatch: + inputs: + window_days: + description: "窗口天数覆盖(空=7)" + default: "" + sample_size: + description: "抽样数覆盖(空=3)" + default: "" + +permissions: + issues: write + contents: read + +jobs: + sli: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: 自测(T2/T3/T5 离线) + run: bash scripts/sli-report.sh --self-test + - name: SLI 采集 + 周报 + 抽样审计 + env: + GH_TOKEN: ${{ secrets.GOVERNANCE_TOKEN }} + SLI_WINDOW: ${{ inputs.window_days }} + SLI_SAMPLE_SIZE: ${{ inputs.sample_size }} + run: | + exit_code=0 + bash scripts/sli-report.sh || exit_code=$? + # 0=正常 | 1=阈值升级触发(已开 P1——运行可见红)| 2=基础设施故障(fail-closed) + if [ "$exit_code" -eq 2 ]; then echo "::error::SLI 采集基础设施故障(fail-closed)"; exit 1; fi + exit 0 From 0290261ce4152aedcef53f617a1b5ca5badb87f9 Mon Sep 17 00:00:00 2001 From: randypanding <66171646+randypanding@users.noreply.github.com> Date: Fri, 21 Aug 2026 20:51:56 +0800 Subject: [PATCH 3/4] =?UTF-8?q?chore:=20ADR=20=E7=BC=96=E5=8F=B7=E5=AF=B9?= =?UTF-8?q?=E9=BD=90=200059=EF=BC=880049=20=E8=A2=AB=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E5=8D=A0=E7=94=A8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/sli-report.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/sli-report.sh b/scripts/sli-report.sh index 605c56c..baab0a0 100644 --- a/scripts/sli-report.sh +++ b/scripts/sli-report.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# sli-report.sh —— 自动合并 SLI 采集 + 每周抽样审计(P3-4 / .github #98,ADR-0049) +# sli-report.sh —— 自动合并 SLI 采集 + 每周抽样审计(P3-4 / .github #98,ADR-0059) # # 指标(窗口默认 7 天,SLI_WINDOW 天): # auto_merge_rate = agent 身份合并的 PR / 全部合并 PR(agent=cloudbrid-agent[bot]/app/…) @@ -195,7 +195,7 @@ if [[ -n "$SAMPLES" ]]; then $SAMPLES 审计 checklist(每样本):改动是否与宣称相符 / 门禁判定是否正确 / 有无事后发现问题。 -发现回流:归因后回写 SLI 指标,必要时开门禁补强 issue。(#98,ADR-0049)" || infra "审计 issue 创建失败" +发现回流:归因后回写 SLI 指标,必要时开门禁补强 issue。(#98,ADR-0059)" || infra "审计 issue 创建失败" else echo "::notice::窗口内无 agent 合并 PR——抽样审计本期跳过" fi From dc660d058a00ed622ddd0d5903ce6bf59b0aac6b Mon Sep 17 00:00:00 2001 From: randypanding <66171646+randypanding@users.noreply.github.com> Date: Fri, 21 Aug 2026 21:01:43 +0800 Subject: [PATCH 4/4] =?UTF-8?q?chore:=20retrigger=EF=BC=88ADR-0059=20?= =?UTF-8?q?=E5=B7=B2=E5=85=A5=20agent-registry=20main=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/sli-report.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/sli-report.sh b/scripts/sli-report.sh index baab0a0..e411620 100644 --- a/scripts/sli-report.sh +++ b/scripts/sli-report.sh @@ -206,3 +206,5 @@ if [[ "$ESCALATE" == "ESCALATE" ]]; then && exit 1 fi exit 0 + +# retrigger(ADR-0059 已合并,org-gate 需新事件)