From 17ac8d4c26ab5eed91416b4cc9f6f06b663bb77a Mon Sep 17 00:00:00 2001 From: randypanding Date: Sat, 29 Aug 2026 08:24:12 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B3=A2=E6=AC=A1=E9=A2=84=E7=AE=97?= =?UTF-8?q?=E6=89=A7=E6=B3=95=E9=97=AD=E7=8E=AF=E2=80=94=E2=80=94conductor?= =?UTF-8?q?=20T7=20=E8=A7=A3=E6=9E=90=E5=AD=98=E5=82=A8=20+=20cost-check?= =?UTF-8?q?=20=E6=B3=A2=E6=AC=A1=E8=A7=86=E5=9B=BE=E7=A1=AC=E5=81=9C?= =?UTF-8?q?=E4=B8=89=E4=BB=B6=E5=A5=97=EF=BC=88IR-0006=20W2-C3=20=E5=90=8E?= =?UTF-8?q?=E5=8D=8A=20/=20BEH-07=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - conductor T7(wave-planned→ready)增设 wave schema 谓词:卡 body 波次块非法 → 拒绝就绪(回退标签,fail-closed);合法 wave-meta 解析后以幂等标记 wave-meta:v1 评论落卡(解析存储,W2-C3/IFACE-03) - cost-check 新增波次预算通道 wave_channel_check:open type:card 的 budget 块 vs 统一账本按 subject 聚合(evidence-query 三源先验链);hard-stop 卡超限进 与 Actions/LLM 同一硬停档三件套(熔断变量+撤 auto-merge+P0,ADR-0040 复位 流程不变);链断/块非法/清单拉取失败=INFRA fail-closed - butler-audit.sh 修复:_shadow_emit 临时文件清理弃 RETURN trap(bash 5.2 实测 trap 穿透调用方返回,set -u 下 evf 越界必炸——W2-C3 全脚本单测抓出,CI 下一轮 cost-check 会红),改显式 rm - 测试:test-cost-wave-channel.sh(标记对提取防测试测影子 + 全脚本集成段锁 三件套调用路径 + AC-9b tripped 尾行落影子账本) ADR-0103(统一证据账本)/ ADR-0040(熔断复位流程) --- .github/workflows/conductor.yml | 64 ++++++++ governance/butler-audit.sh | 14 +- governance/cost-check.sh | 95 +++++++++++- governance/tests/test-cost-wave-channel.sh | 172 +++++++++++++++++++++ 4 files changed, 335 insertions(+), 10 deletions(-) create mode 100644 governance/tests/test-cost-wave-channel.sh diff --git a/.github/workflows/conductor.yml b/.github/workflows/conductor.yml index 3d43768..79acfca 100644 --- a/.github/workflows/conductor.yml +++ b/.github/workflows/conductor.yml @@ -11,6 +11,9 @@ name: conductor # arbiter 裁决(宪法 §11 唤醒矩阵事件行"仲裁请求处理(/claim 等,转 arbiter)"); # 仲裁是叠加授权层,transitions.yaml 转移表语义不变。三态:0=allow 继续原动作、 # 1=deny 审计 no-op(对齐 silent-drop)、2=infra run 红灯 fail-closed 不放行。 +# W2-C3(.github#414 / BEH-07 / IFACE-03):T7 增设 wave schema 谓词——卡 body +# 波次块(budget/capabilities/evidence)非法拒绝就绪(fail-closed);合法 wave-meta +# 解析后以幂等标记(wave-meta:v1)评论落卡(conductor 解析存储)。 on: issues: types: [labeled] @@ -55,6 +58,7 @@ jobs: path: gov sparse-checkout: | governance/transitions.yaml + governance/wave_schema.py scripts/gh-app-token.sh persist-credentials: false # arbiter 受信 checkout(ADR-0055):conductor 只在 main 上下文运行事件路由, @@ -437,6 +441,44 @@ jobs: api(E["APP_TOKEN"], f"/repos/{REPO}/issues/{ISSUE}/labels/state%3Awave-planned", "DELETE") raise SystemExit(0) + # ---- W2-C3(.github#414 / BEH-07 / IFACE-03):T7 wave schema 谓词 ---- + # 卡 body 波次块(budget/capabilities/evidence)存在但非法 → 拒绝就绪 + # (fail-closed:wave_schema.py validate exit 3;无块=缺省无约束语义,放行)。 + # 合法且含块 → 解析 wave-meta(结构化 JSON),转移成功后以幂等标记评论 + # 存储在本卡(下游 cost-check 波次视图按卡 body 同源对账,评论=解析结果的 + # 机器可读镜像)。 + wave_meta_json = None + if t["id"] == "T7": + import tempfile + with tempfile.NamedTemporaryFile("w", suffix=".md", delete=False, + encoding="utf-8") as btf: + btf.write(iss.get("body") or "") + body_path = btf.name + try: + rc_v = subprocess.call(["python3", "gov/governance/wave_schema.py", + "validate", "--body-file", body_path]) + if rc_v == 3: + audit("verdict=DENIED-wave-schema-invalid T7 拒绝——波次块非法" + "(W2-C3 IFACE-03;fail-closed,标签已回退)") + api(E["APP_TOKEN"], f"/repos/{REPO}/issues/{ISSUE}/labels/state%3Aready", "DELETE") + raise SystemExit(0) + if rc_v != 0: + audit(f"verdict=ABORT wave_schema validate rc={rc_v}(fail-closed——infra)") + raise SystemExit(1) + p = subprocess.run(["python3", "gov/governance/wave_schema.py", "parse", + "--body-file", body_path, "--card", f"{REPO}#{ISSUE}"], + capture_output=True, text=True) + finally: + os.unlink(body_path) + if p.returncode != 0: + audit(f"verdict=ABORT wave_schema parse rc={p.returncode}(fail-closed——infra)") + raise SystemExit(1) + try: + wave_meta_json = json.loads(p.stdout or "{}") + except json.JSONDecodeError: + audit("verdict=ABORT wave-meta 解析输出非 JSON(fail-closed)") + raise SystemExit(1) + # ---- ADR-0085:T8 卡完成谓词(存在绑定本卡且已合并的 PR)---- def check_merged_pr(issue_number): """卡完成谓词:存在 body 含 Card: # 绑定行且已合并的 PR。 @@ -542,6 +584,28 @@ jobs: f"claim->in-progress assignee={actor}") else: swap_state(t["from_state"], t["to_state"]) + # W2-C3(.github#414):T7 解析存储——合法 wave-meta 以幂等标记评论 + # 落卡(重投/重复标签不重复评论;写失败=fail-closed 红灯,状态已切 + # 但存储缺失在 run 日志+on-failure 评论双面可见) + if t["id"] == "T7" and wave_meta_json: + marker = "wave-meta:v1" + st_cm, cms = api(E["APP_TOKEN"], + f"/repos/{REPO}/issues/{ISSUE}/comments?per_page=100") + if st_cm != 200: + audit(f"verdict=ABORT wave-meta 评论读取失败 HTTP {st_cm}(fail-closed)") + raise SystemExit(1) + if not any(marker in (c.get("body") or "") for c in cms or []): + st_pc, _ = api(E["APP_TOKEN"], + f"/repos/{REPO}/issues/{ISSUE}/comments", "POST", + {"body": f"\n" + "**wave-meta(T7 解析存储,IR-0006 W2-C3 / IFACE-03)**\n" + "```json\n" + + json.dumps(wave_meta_json, ensure_ascii=False, sort_keys=True) + + "\n```"}) + if st_pc not in (200, 201): + audit(f"verdict=ABORT wave-meta 评论写失败 HTTP {st_pc}(fail-closed)") + raise SystemExit(1) + audit("wave-meta 评论已存储(T7 解析存储,W2-C3)") out.write("invoke=none\n") audit(f"event={ev} transition={t['id']} sender_role={role} verdict=ALLOWED " f"{t['from_state']}->{t['to_state']} action=noop") diff --git a/governance/butler-audit.sh b/governance/butler-audit.sh index 21e9c90..7f4c06e 100644 --- a/governance/butler-audit.sh +++ b/governance/butler-audit.sh @@ -118,15 +118,17 @@ audit_emit() { } # 影子双写(BEH-03):schema v1 判定记录落本地影子账本(链式 hash,写入器独占) +# 注:临时文件清理用显式 rm 不用 RETURN trap——bash 5.2 实测 RETURN trap 会穿透 +# 到调用方函数返回(traps 不随函数作用域恢复),set -u 环境下 $evf 越界即炸 +# (W2-C3 全脚本单测抓出;trap 版曾在 cost-check set -u 下必现 abort)。 _shadow_emit() { [[ -n "$_BUTLER_PY" ]] || return 0 # 无 python 环境:影子无法成链——原层照常(极端降级) local butler="$1" outcome="$2" actions="$3" - local here shadow evf + local here shadow evf ev_rc=0 here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" shadow="${BUTLER_SHADOW_FILE:-$here/butler/shadow-evidence.jsonl}" evf="$(mktemp)" - trap 'rm -f "$evf"' RETURN - "$_BUTLER_PY" - "$evf" "$butler" "$outcome" <<'PYEOF' || { echo "FATAL: 影子事件构造失败" >&2; return 2; } + "$_BUTLER_PY" - "$evf" "$butler" "$outcome" <<'PYEOF' || ev_rc=$? import datetime, json, sys ev = { "ts": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), @@ -137,10 +139,16 @@ ev = { } open(sys.argv[1], "w", encoding="utf-8").write(json.dumps(ev, ensure_ascii=False)) PYEOF + if [[ $ev_rc -ne 0 ]]; then + rm -f "$evf" + echo "FATAL: 影子事件构造失败" >&2; return 2 + fi if ! "$_BUTLER_PY" "$here/evidence_shadow.py" append --file "$shadow" --event-file "$evf" >/dev/null; then + rm -f "$evf" echo "FATAL: 影子账本写入失败($shadow)——fail-closed(BEH-01 双写不一致当场可见)" >&2 return 2 fi + rm -f "$evf" } # ---------- CLI 模式(bash butler-audit.sh ...;source 时不执行) ---------- diff --git a/governance/cost-check.sh b/governance/cost-check.sh index f1767ec..b12add0 100644 --- a/governance/cost-check.sh +++ b/governance/cost-check.sh @@ -9,13 +9,19 @@ # LLM token 通道(W2-C3 .github#216,ADR-0062):data_source=ciw-metering 时按角色档归账 # (CI-Workflows metering-ledger 分支 → metering.py aggregate,先验链后归账); # 链断/拉取失败 = INFRA fail-closed(exit 2),不静默归零不盲熔断。 +# 波次预算通道(W2-C3 .github#414,BEH-07 / ADR-0103):open type:card 卡 body 的 +# budget 块(四元组+on_exceed)vs 统一账本按 subject 聚合(evidence-query 三源 +# 先验链后归账);hard-stop 卡超限 → 与 Actions/LLM 同一硬停档三件套(熔断变量+ +# 撤 auto-merge+P0,ADR-0040 复位流程不变);链断/块非法 = INFRA fail-closed。 # 熔断消费点:agent 派发/automerge 前置检查(AGENTS.md 行为契约)+ auto-fix-limit.sh # 每轮机器执法撤 auto-merge。复位仅人工(owner PATCH/DELETE 变量 + P0 issue 留评论); # 本脚本观察到"变量已复位且用量 <100%"后自动关闭 P0 issue(复位留痕=issue 评论历史)。 # # 用法: GH_TOKEN= bash cost-check.sh # 注入(T2,不依赖真实超支): COST_USAGE_MINUTES_OVERRIDE / COST_QUOTA_MINUTES_OVERRIDE / -# COST_LLM_TOKENS_USED_OVERRIDE / COST_LLM_TOKENS_QUOTA_OVERRIDE / COST_DRY_RUN=1(只报告不写) +# COST_LLM_TOKENS_USED_OVERRIDE / COST_LLM_TOKENS_QUOTA_OVERRIDE / +# COST_WAVE_CARDS_FILE(本地卡清单 JSON)/ COST_WAVE_LEDGER_DIR(本地统一账本目录)/ +# COST_DRY_RUN=1(只报告不写) # 退出码: 0=未达阈值 | 1=触发告警/熔断(运行变红=可见信号)| 2=基础设施故障(fail-closed) set -uo pipefail @@ -193,6 +199,57 @@ llm_channel_account() { } # @w2c3-llm-channel-end +# ---------- 波次预算对账(W2-C3 .github#414,BEH-07 / ADR-0103:统一账本 subject 聚合) ---------- +# @w2c3-wave-channel-begin(governance/tests/test-cost-wave-channel.sh 按标记对提取本函数体 +# 离线单测——标记对缺失=测试红,防"测试测影子",同 llm-channel 模式) +wave_channel_check() { + # → stdout 单行 "WAVE-EXCEEDED超限行json" | "WAVE-OK说明" | "INFRA说明"。 + # 卡面=$GOV_REPO open type:card(body 含 budget 块才有约束,缺省=无预算语义); + # 聚合源=统一账本(evidence-query.sh 三源拉取+验链→shadow-evidence-unified.jsonl, + # 文件名命中 wave_check 的 shadow-evidence-*.jsonl glob——先验链后归账,同 LLM 通道)。 + # 本函数经命令替换调用(子 shell),不直接调 infra/ok(计数会丢)——标签由调用方 + # 在父 shell 落账。env 注入(T2):COST_WAVE_CARDS_FILE=本地卡清单 JSON、 + # COST_WAVE_LEDGER_DIR=本地账本目录(shadow-evidence-*.jsonl)。 + local cards="${COST_WAVE_CARDS_FILE:-}" led="${COST_WAVE_LEDGER_DIR:-}" out rc=0 + if [[ -z "$cards" ]]; then + cards=$(mktemp) || { printf 'INFRA\t波次卡清单临时文件创建失败\n'; return 0; } + if ! "$GH" issue list --repo "$GOV_REPO" --state open --label type:card --limit 300 \ + --json number,body >"$cards" 2>/dev/null; then + printf 'INFRA\t波次卡清单拉取失败(%s open type:card)——预算面不可知,fail-closed\n' "$GOV_REPO" + return 0 + fi + fi + if [[ -z "$led" ]]; then + led=$(mktemp -d) || { printf 'INFRA\t统一账本临时目录创建失败\n'; return 0; } + if ! bash "$DIR/evidence-query.sh" >"$led/shadow-evidence-unified.jsonl" 2>"$led/eq.err"; then + printf 'INFRA\t统一账本查询失败(链断/拉取失败——不可信数据不判定,不盲熔断):%.200s\n' \ + "$(tail -c 200 "$led/eq.err" 2>/dev/null)" + return 0 + fi + fi + out=$(python3 "$DIR/wave_schema.py" wave-check --cards "$cards" --ledger-dir "$led" 2>/dev/null) || rc=$? + if [[ $rc -eq 2 ]]; then + printf 'INFRA\twave-check 执行失败(参数/环境 rc=2)\n'; return 0 + fi + python3 -c 'import json,sys +rows = json.loads(sys.stdin.read() or "[]") +bad = [r for r in rows if r.get("error")] +if bad: + print("INFRA\t波次卡块非法(预算面盲区,fail-closed):" + "; ".join(r["card"] + " " + r["error"] for r in bad)) + sys.exit(0) +hard = [r for r in rows if r.get("exceeded_dims") and r.get("on_exceed") == "hard-stop"] +warn = [r for r in rows if r.get("exceeded_dims") and r.get("on_exceed") == "warn"] +if hard: + print("WAVE-EXCEEDED\t" + json.dumps(hard, ensure_ascii=False)) + sys.exit(0) +nb = sum(1 for r in rows if not r.get("error")) +extra = "" +if warn: + extra = ";warn 超限(只报告不判定):" + ", ".join(r["card"] + ":" + "+".join(r["exceeded_dims"]) for r in warn) +print("WAVE-OK\t预算卡 %d 张对账无 hard-stop 超限%s" % (nb, extra))' <<<"$out" +} +# @w2c3-wave-channel-end + PCT_TOK="" USED_TOK="" LLM_ROLES="" @@ -226,6 +283,24 @@ else infra "LLM token 数据源未知:$LT_SOURCE(policy cost.llm_tokens.data_source 无此形态)" fi +# ---------- 波次预算通道(W2-C3 .github#414,BEH-07:统一账本按 subject 聚合对账) ---------- +STOP_WAVE="False" +WAVE_EXCEEDED="" +WAVE_SUMMARY="" +WLINE=$(wave_channel_check) || true +IFS=$'\t' read -r WTAG WVAL <<<"$WLINE" +case "$WTAG" in + WAVE-EXCEEDED) + # BEH-07:hard-stop 卡超限 → 进硬停档三件套(下方与 Actions/LLM 同档执法) + STOP_WAVE="True"; WAVE_EXCEEDED="$WVAL" + WAVE_SUMMARY="波次预算超限(hard-stop,BEH-07): $WVAL" + ok "$WAVE_SUMMARY" + ;; + WAVE-OK) WAVE_SUMMARY="波次预算(波次视图): $WVAL"; ok "$WAVE_SUMMARY" ;; + INFRA) infra "$WVAL" ;; + *) infra "波次通道输出不可解析(期望 WAVE-EXCEEDED/WAVE-OK/INFRA 标签):$WLINE" ;; +esac + # ---------- 熔断当前状态 ---------- BREAKER_SET=0 # 读取 rc 单独捕获(W2-C3 顺带修复的潜在缺陷):变量存在且 value=false(人工复位后 @@ -287,23 +362,29 @@ set_breaker() { # PATCH 已有 / POST 新建(404 时) fi } -# ---------- 硬停档(任一指标 ≥100%) ---------- -if [[ "$STOP_MIN" == "True" || "$STOP_TOK" == "True" ]]; then +# ---------- 硬停档(任一指标 ≥100%,或波次预算 hard-stop 卡超限——BEH-07) ---------- +if [[ "$STOP_MIN" == "True" || "$STOP_TOK" == "True" || "$STOP_WAVE" == "True" ]]; then TRIPPED=1 - act "硬停档触发(Actions=${PCT_MIN}% LLM=${PCT_TOK:--}%)——置 $CB_VARIABLE + 撤 auto-merge + P0" + act "硬停档触发(Actions=${PCT_MIN}% LLM=${PCT_TOK:--}% 波次超限=${STOP_WAVE})——置 $CB_VARIABLE + 撤 auto-merge + P0" set_breaker strip_all_automerge label_ensure "$GOV_REPO" cost-circuit-breaker b60205 P0_EXISTING=$(gov_open_issues cost-circuit-breaker | grep -m1 "成本熔断" | cut -f1) + if [[ "$STOP_WAVE" == "True" ]]; then + P0_TITLE="P0 成本熔断:波次预算超限达硬停档($CB_VARIABLE 已置位,BEH-07)" + else + P0_TITLE="P0 成本熔断:Actions 分钟 ${PCT_MIN}% 达硬停档($CB_VARIABLE 已置位)" + fi P0_BODY="P0:额度/成本熔断已置位(ADR-0040,运行 $(date -u +%FT%TZ))。 - Actions 分钟($YEAR-$MONTH): $USED_MIN / $AM_QUOTA = ${PCT_MIN}%(阈值 $AM_STOP%)${PCT_TOK:+ -- ${LLM_SUMMARY:-LLM token: $USED_TOK}(阈值 $LT_STOP%,ADR-0062 归账通道)} +- ${LLM_SUMMARY:-LLM token: $USED_TOK}(阈值 $LT_STOP%,ADR-0062 归账通道)}${WAVE_EXCEEDED:+ +- 波次预算超限(BEH-07,ADR-0103 统一账本 subject 聚合): $WAVE_EXCEEDED} - 已执行:org 变量 \`$CB_VARIABLE\`=true;全部 open PR 的 auto-merge 已撤销。 - 效果:agent 派发与 automerge 前置检查将拒绝启动(AGENTS.md);auto-fix-limit 每轮机器执法撤销新 enable。 处置(仅 $CB_RESET_BY,人工): -1. 排查用量根因(失控循环查 auto-fix-limit 的 issue 历史); +1. 排查用量根因(失控循环查 auto-fix-limit 的 issue 历史;波次超限查上列卡的 dispatch/计量记录——收口超限卡或调 budget 块后再复位); 2. 复位:\`gh api -X PATCH orgs/$ORG/actions/variables/$CB_VARIABLE -f name=$CB_VARIABLE -f value=false\`(或 DELETE 该变量); 3. 在本 issue 留复位评论(留痕);cost-check 确认变量复位且用量 <${AM_STOP}% 后自动关闭本 issue。" if [[ -n "$P0_EXISTING" ]]; then @@ -312,7 +393,7 @@ if [[ "$STOP_MIN" == "True" || "$STOP_TOK" == "True" ]]; then fi else mutate "$GH" issue create --repo "$GOV_REPO" \ - --title "P0 成本熔断:Actions 分钟 ${PCT_MIN}% 达硬停档($CB_VARIABLE 已置位)" \ + --title "$P0_TITLE" \ --body "$P0_BODY" --label cost-circuit-breaker >/dev/null 2>&1 \ || infra "P0 issue 开立失败" fi diff --git a/governance/tests/test-cost-wave-channel.sh b/governance/tests/test-cost-wave-channel.sh new file mode 100644 index 0000000..c374317 --- /dev/null +++ b/governance/tests/test-cost-wave-channel.sh @@ -0,0 +1,172 @@ +#!/usr/bin/env bash +# test-cost-wave-channel.sh —— cost-check 波次预算通道单测(W2-C3 .github#414,BEH-07 / ADR-0103) +# +# 从 cost-check.sh 按 @w2c3-wave-channel 标记对提取 wave_channel_check 函数体 +# (不复制实现——防"测试测影子",同 test-cost-llm-channel.sh 模式;标记对缺失=fail-closed 红), +# 用注入卡清单/本地统一账本(T2 注入通道)断言通道契约: +# WAVE-EXCEEDED hard-stop 卡超限 → 标签+超限行 JSON(含 tenant 分离聚合) +# WAVE-OK 无超限(warn-only 只报告;无预算卡不计约束) +# INFRA 卡清单拉取失败 / 波次块非法(预算面盲区,fail-closed) +# 另含全脚本集成段(桩 gh 记录调用日志,非 DRY——桩吸收全部写操作):波次超限 → +# 硬停档三件套真实调用路径(PATCH 熔断变量 + label cost-circuit-breaker + P0 issue +# create)+ exit 1 + 审计尾行 outcome=tripped 落影子账本(AC-9b:超限硬停实测记录 +# 进账本——机制面)。 +# 真实三源账本拉取/验链由 evidence-query.sh 既有测试与 CI 覆盖;本测试锁 +# cost-check 侧消费契约与 fail-closed 方向。用法:bash governance/tests/test-cost-wave-channel.sh +set -uo pipefail +DIR="$(cd "$(dirname "$0")/.." && pwd)" +FAILS=0 +pass() { echo "PASS $1"; } +fail() { echo "FAIL $1"; FAILS=$((FAILS+1)); } + +# --- 提取被测函数(标记对缺失=fail-closed:测试与实现脱钩即红) --- +SRC="$DIR/cost-check.sh" +[[ -f "$SRC" ]] || { echo "FATAL: cost-check.sh 不存在"; exit 2; } +EXTRACTED=$(awk '/@w2c3-wave-channel-begin/{f=1} f{print} /@w2c3-wave-channel-end/{exit}' "$SRC") +if ! grep -q '^wave_channel_check()' <<<"$EXTRACTED"; then + echo "FATAL: 标记对内未找到 wave_channel_check 定义(提取失效——实现与测试脱钩)"; exit 2 +fi + +# --- 桩环境(函数在命令替换子 shell 外定义;ok/infra 桩防实现漂移误伤主脚本计数) --- +INFRAS=0 +infra() { echo "INFRA $1"; INFRAS=$((INFRAS+1)); } +ok() { echo "OK $1"; } +GH=gh +GOV_REPO="Cloudbird-Software/.github" +eval "$EXTRACTED" + +TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT +# python3 本地垫片(Windows 商店 stub 环境);CI(ubuntu)python3 直用不受影响 +if ! python3 -c 'import sys' >/dev/null 2>&1; then + mkdir -p "$TMP/bin" + printf '#!/usr/bin/env bash\nexec python "$@"\n' >"$TMP/bin/python3"; chmod +x "$TMP/bin/python3" + export PATH="$TMP/bin:$PATH" +fi + +run_chan() { # 捕获函数输出(注入变量控制数据源) + WOUT=$(wave_channel_check) +} + +# ---- 1) WAVE-EXCEEDED:hard-stop 卡超限 → 标签+超限行 JSON(tenant 分离聚合) ---- +cat >"$TMP/cards.json" <<'EOF' +[ + {"number": 500, "body": "## budget(波次预算)\nusd: 10.0\ntokens: 100000\non_exceed: hard-stop"}, + {"number": 501, "body": "## budget(波次预算)\ntokens: 999999\non_exceed: warn"}, + {"number": 502, "body": "## 任务\n无预算卡"} +] +EOF +mkdir -p "$TMP/ledger" +cat >"$TMP/ledger/shadow-evidence-unified.jsonl" <<'EOF' +{"ts":"2026-08-29T01:00:00Z","kind":"cost","action":"cost.dispatch-burst","verdict":"pass","subject":{"card":"Cloudbird-Software/.github#500","tenant":"cloudbird-internal"},"cost":{"tokens":40000,"usd":4.0,"wall_sec":600.0},"seq":1,"prev_hash":null,"hash":"aa"} +{"ts":"2026-08-29T02:00:00Z","kind":"cost","action":"cost.dispatch-burst","verdict":"pass","subject":{"card":"Cloudbird-Software/.github#500","tenant":"tenant-b"},"cost":{"tokens":30000,"usd":7.5,"wall_sec":100.0},"seq":2,"prev_hash":"aa","hash":"bb"} +{"ts":"2026-08-29T03:00:00Z","kind":"cost","action":"cost.dispatch-burst","verdict":"pass","subject":{"card":"Cloudbird-Software/.github#501","tenant":"cloudbird-internal"},"cost":{"tokens":500,"usd":0.1,"wall_sec":10.0},"seq":3,"prev_hash":"bb","hash":"cc"} +EOF +COST_WAVE_CARDS_FILE="$TMP/cards.json" COST_WAVE_LEDGER_DIR="$TMP/ledger" run_chan +if [[ "${WOUT%%$'\t'*}" == "WAVE-EXCEEDED" ]]; then + pass "hard-stop 超限 → WAVE-EXCEEDED 标签" +else fail "应 WAVE-EXCEEDED,得到:$WOUT"; fi +python3 - "$WOUT" <<'PYEOF' && pass "超限行 JSON 断言(tenant 分离聚合 AC-9b + warn 卡不入 hard 列表)" || fail "超限行 JSON 断言" +import json, sys +hard = json.loads(sys.argv[1].split("\t", 1)[1]) +assert len(hard) == 1, hard +h = hard[0] +assert h["card"] == "Cloudbird-Software/.github#500", h +assert h["usage_by_tenant"]["cloudbird-internal"]["usd"] == 4.0, h +assert h["usage_by_tenant"]["tenant-b"]["usd"] == 7.5, h +assert h["usage_total"]["usd"] == 11.5, h +assert h["exceeded_dims"] == ["usd"], h +assert h["on_exceed"] == "hard-stop", h +PYEOF + +# ---- 2) WAVE-OK:无 hard-stop 超限(warn-only 报告;无预算卡不构成约束) ---- +cat >"$TMP/cards2.json" <<'EOF' +[ + {"number": 501, "body": "## budget(波次预算)\ntokens: 100\non_exceed: warn"}, + {"number": 502, "body": "## 任务\n无预算卡"} +] +EOF +COST_WAVE_CARDS_FILE="$TMP/cards2.json" COST_WAVE_LEDGER_DIR="$TMP/ledger" run_chan +if [[ "${WOUT%%$'\t'*}" == "WAVE-OK" ]] && grep -q "warn 超限" <<<"$WOUT" \ + && grep -q "501" <<<"$WOUT"; then + pass "warn-only 超限 → WAVE-OK(只报告不判定)" +else fail "warn-only 应 WAVE-OK+warn 报告,得到:$WOUT"; fi + +# ---- 3) 空账本目录 → WAVE-OK 零超限(无记录=无用量,不是 infra) ---- +mkdir -p "$TMP/ledger-empty" +COST_WAVE_CARDS_FILE="$TMP/cards.json" COST_WAVE_LEDGER_DIR="$TMP/ledger-empty" run_chan +if [[ "${WOUT%%$'\t'*}" == "WAVE-OK" ]]; then + pass "空账本目录 → WAVE-OK(零用量非 infra)" +else fail "空账本应 WAVE-OK,得到:$WOUT"; fi + +# ---- 4) INFRA:波次块非法(预算面盲区——fail-closed 方向) ---- +cat >"$TMP/cards-bad.json" <<'EOF' +[ + {"number": 503, "body": "## budget(波次预算)\neuro: 1"} +] +EOF +COST_WAVE_CARDS_FILE="$TMP/cards-bad.json" COST_WAVE_LEDGER_DIR="$TMP/ledger" run_chan +if [[ "${WOUT%%$'\t'*}" == "INFRA" ]] && grep -q "块非法" <<<"$WOUT"; then + pass "波次块非法 → INFRA(fail-closed:预算面盲区可见)" +else fail "块非法应 INFRA,得到:$WOUT"; fi + +# ---- 5) INFRA:卡清单拉取失败(无注入 + 桩 gh 失败) ---- +GHFAIL="$TMP/ghfail" +printf '#!/usr/bin/env bash\necho "gh: failure" >&2\nexit 1\n' >"$GHFAIL"; chmod +x "$GHFAIL" +GH="$GHFAIL" +run_chan 2>/dev/null +GH=gh +if [[ "${WOUT%%$'\t'*}" == "INFRA" ]] && grep -q "卡清单拉取失败" <<<"$WOUT"; then + pass "卡清单拉取失败 → INFRA(预算面不可知)" +else fail "清单失败应 INFRA,得到:$WOUT"; fi + +# ---- 6) 集成段:全脚本(桩 gh 记录调用日志,吸收全部写操作)—— 波次超限 → 硬停三件套 ---- +# 桩 gh:billing=0 用量(数值已按 --jq 口径)、熔断变量 404、issue/pr 清单空; +# 全部调用落 $GHSTUB_LOG 供三件套调用路径断言(BEH-07:置变量+P0 issue)。 +# 匹配按子命令位置精确判($1/$2)——宽通配会把 P0 issue body 里的 +# "actions/variables/AUTO_MERGE_DISABLED" 复位说明误判成变量读调用。 +GHSTUB="$TMP/ghstub" +export GHSTUB_LOG="$TMP/ghstub.log" +cat >"$GHSTUB" <<'EOF' +#!/usr/bin/env bash +printf '%s\n' "$*" >>"${GHSTUB_LOG:-/dev/null}" +if [[ "$1" == "api" ]]; then + case "$2" in + *settings/billing/usage*) echo 0 ;; + *actions/variables/AUTO_MERGE_DISABLED*) echo "gh: Not Found (HTTP 404)" >&2; exit 1 ;; + *) echo "{}" ;; + esac +else + echo "[]" +fi +EOF +chmod +x "$GHSTUB" +OUT2=$(mktemp) +COST_WAVE_CARDS_FILE="$TMP/cards.json" \ +COST_WAVE_LEDGER_DIR="$TMP/ledger" \ +COST_USAGE_MINUTES_OVERRIDE=0 \ +COST_LLM_TOKENS_USED_OVERRIDE=1000 \ +GH="$GHSTUB" \ +GH_TOKEN=stub-token \ +BUTLER_SHADOW_FILE="$TMP/butler-shadow.jsonl" \ +bash "$SRC" >"$OUT2" 2>"$OUT2.err"; RC2=$? +if [[ $RC2 -eq 1 ]]; then + pass "波次超限全脚本(桩写面)→ exit 1(运行变红=可见信号)" +else fail "全脚本应 exit 1(rc=$RC2)"; tail -n 20 "$OUT2" "$OUT2.err"; fi +if grep -q "硬停档触发" "$OUT2" && grep -q "波次超限=True" "$OUT2"; then + pass "硬停档 act 行含波次超限标记(BEH-07 进三件套)" +else fail "缺硬停档触发/波次超限 act 行"; fi +if grep -q "PATCH" "$GHSTUB_LOG" && grep -q "actions/variables/AUTO_MERGE_DISABLED" "$GHSTUB_LOG"; then + pass "三件套①:org 熔断变量 PATCH 置位调用已发(set_breaker)" +else fail "熔断变量置位调用未见"; fi +if grep -q "issue create" "$GHSTUB_LOG" && grep -q "cost-circuit-breaker" "$GHSTUB_LOG"; then + pass "三件套③:P0 issue 开立调用已发(label cost-circuit-breaker)" +else fail "P0 issue 开立调用未见"; fi +if grep -q "pr list" "$GHSTUB_LOG"; then + pass "三件套②:auto-merge 撤销清单拉取已发(strip_all_automerge 扫描面)" +else fail "auto-merge 撤销扫描未见"; fi +if grep -q '"verdict":"tripped"' "$TMP/butler-shadow.jsonl" 2>/dev/null \ + || grep -q '"verdict": "tripped"' "$TMP/butler-shadow.jsonl" 2>/dev/null; then + pass "超限硬停审计尾行落影子账本(AC-9b:outcome=tripped 记录进账本——机制面)" +else fail "影子账本未见 tripped 尾行"; fi + +[[ $FAILS -eq 0 ]] && echo "== test-cost-wave-channel.sh 全绿 ==" || { echo "== test-cost-wave-channel.sh 失败 $FAILS 处 =="; exit 1; }