From f69be05e7c73fc4c85f8fd1473dd0f412b4107dd Mon Sep 17 00:00:00 2001 From: randypanding Date: Sat, 22 Aug 2026 12:58:08 +0800 Subject: [PATCH 1/9] fix(governance): gate.yml hygiene pin refresh to v1 current target (#259 C6-a, ADR-0021) hygiene.yml@9c20d43... no longer matches any v1 tag (v1 now points to 61191f87c537f6e887695517121c6e530a838261). Refresh pin to current v1 target. Reference: template-service PR #39 same fix pattern. Card: Cloudbird-Software/.github#259 Ref: ADR-0021 --- .github/workflows/gate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gate.yml b/.github/workflows/gate.yml index 4f1f3b5..c7ca0be 100644 --- a/.github/workflows/gate.yml +++ b/.github/workflows/gate.yml @@ -20,7 +20,7 @@ jobs: # 复用 CI-Workflows hygiene(大文件/凭据类文件/gitleaks 全历史/zizmor 工作流审计) # 钉 commit hash(zizmor unpinned-uses:可变 tag 不算钉住;ADR-0021): # hash 即 CI-Workflows v1 tag 当前指向,升级 v1 须同步换 hash - uses: Cloudbird-Software/CI-Workflows/.github/workflows/hygiene.yml@9c20d43d60e9f9d1438f171aeee8e4dbdf02c08b # v1 + uses: Cloudbird-Software/CI-Workflows/.github/workflows/hygiene.yml@61191f87c537f6e887695517121c6e530a838261 # v1 gate: runs-on: ubuntu-latest From 8e68615da40bfb946526829206f607dff534233b Mon Sep 17 00:00:00 2001 From: randypanding Date: Mon, 24 Aug 2026 08:57:56 +0800 Subject: [PATCH 2/9] =?UTF-8?q?feat(governance):=20T-14=20=E6=9C=BA?= =?UTF-8?q?=E5=99=A8=E5=8C=96=E7=AC=AC=E4=B8=80=E9=9D=A2=20+=20ruleset/req?= =?UTF-8?q?uired=20check=20=E8=90=BD=E5=9C=B0=E4=BF=AE=E6=AD=A3=EF=BC=88W2?= =?UTF-8?q?-C3=20.github#275=EF=BC=8CADR-0083=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - gate.yml 新增 t14-spec-suite job:specs/** PR 必含含断言的 suite/(fail-closed); 全部 specs/*/suite 在 CI 真实执行(可执行性证明) - specs/ISSUE-263/suite/:IR 自有验收套件(9 项断言——T5/T6、T-14/T-15、AR-10、 ruleset adversary 无 integration_id、verifier_app 登记、holdout 申报;自反断言) - main-protection.json 剔除 adversary 条目非法 integration_id:0(致 apply 422、 线上 ruleset 长期缺 adversary——独立验证发现的断链根因) - drift-check §1 fail-closed:ruleset 清单非数组 → FATAL exit 2(GOVERNANCE_TOKEN 失效期间曾把四个真实 ruleset 全报『不存在』) - g060-guard cron 错峰(0 → 17 分,对齐 workflow 规范) --- .github/workflows/g060-guard.yml | 2 +- .github/workflows/gate.yml | 82 ++++++++++++++ governance/drift-check.sh | 8 ++ governance/rulesets/main-protection.json | 3 +- .../test_ir263_artifacts.cpython-311.pyc | Bin 0 -> 9859 bytes specs/ISSUE-263/suite/test_ir263_artifacts.py | 105 ++++++++++++++++++ 6 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 specs/ISSUE-263/suite/__pycache__/test_ir263_artifacts.cpython-311.pyc create mode 100644 specs/ISSUE-263/suite/test_ir263_artifacts.py diff --git a/.github/workflows/g060-guard.yml b/.github/workflows/g060-guard.yml index ad89ee1..299d8b0 100644 --- a/.github/workflows/g060-guard.yml +++ b/.github/workflows/g060-guard.yml @@ -12,7 +12,7 @@ on: - 'specs/*/suite/**' schedule: # 每 6 小时巡检一次(与 butler 系列对齐) - - cron: '0 */6 * * *' + - cron: '17 */6 * * *' workflow_dispatch: inputs: issue: diff --git a/.github/workflows/gate.yml b/.github/workflows/gate.yml index c7ca0be..d54dd4f 100644 --- a/.github/workflows/gate.yml +++ b/.github/workflows/gate.yml @@ -22,6 +22,88 @@ jobs: # hash 即 CI-Workflows v1 tag 当前指向,升级 v1 须同步换 hash uses: Cloudbird-Software/CI-Workflows/.github/workflows/hygiene.yml@61191f87c537f6e887695517121c6e530a838261 # v1 + t14-spec-suite: + # T-14 第一面(ADR-0083 决策 1/2,testing.yaml T-14,#263 W2-C3 .github#275): + # 1) specs/** 变更的 PR 必须携带同目录 suite/(≥1 非空测试文件且含真实 + # 断言)——缺失即红(合并阻断;fail-closed:files API 失败视同 specs 变更)。 + # 2) suite 可执行性证明:全部 specs/*/suite 在本 job 真实执行(unittest 风格、 + # 零第三方依赖)——"存在但摆拍"的红队层兜底(S1'/S2')之外的机器底线。 + # T-14 第二面(fail-before 逐变更 + 实现 PR 卡测试解析 + holdout 注册校验) + # 随后续波次落地,本 job 只锁第一面。 + runs-on: ubuntu-latest + timeout-minutes: 5 + needs: hygiene + if: always() + permissions: + contents: read + pull-requests: read + steps: + - name: hygiene green? + env: + NEEDS: ${{ toJSON(needs) }} + run: | + echo "$NEEDS" + if echo "$NEEDS" | jq -e '[to_entries[] | select(.value.result != "success")] | length > 0' >/dev/null; then + echo "::error::hygiene 未通过(skipped 不算绿——ADR-0032 严格断言)"; exit 1 + fi + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: T-14 spec PR suite 强制(PR 事件) + if: github.event_name == 'pull_request' + env: + GH_TOKEN: ${{ github.token }} + PR_API: "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}" + run: | + set -euo pipefail + # 分页拉全量文件清单(fail-closed:任何页失败=红——不能因盲而放行) + PAGE=1; SPECS_HIT=0; SPEC_DIRS="" + while :; do + PAGEJSON=$(gh api "$PR_API/files?per_page=100&page=$PAGE") + if ! jq -e 'type == "array"' <<<"$PAGEJSON" >/dev/null 2>&1; then + echo "::error::files API 第 $PAGE 页失败——T-14 判定完整性无法保证(fail-closed)"; exit 1 + fi + N=$(jq 'length' <<<"$PAGEJSON") + DIRS=$(jq -r '[.[].filename | select(startswith("specs/")) | split("/")[1] | select(. != "")] | unique | .[]' <<<"$PAGEJSON") + if [[ -n "$DIRS" ]]; then SPECS_HIT=1; SPEC_DIRS="$(printf '%s\n%s' "$SPEC_DIRS" "$DIRS" | sort -u)"; fi + [[ $N -lt 100 ]] && break + PAGE=$((PAGE+1)) + done + if [[ $SPECS_HIT -ne 1 ]]; then echo "非 specs/** 变更,T-14 presence 检查跳过"; exit 0; fi + MISSING=0 + for d in $SPEC_DIRS; do + SUITE="specs/$d/suite" + if [[ ! -d "$SUITE" ]]; then + echo "::error::T-14:specs/$d 变更但缺 $SUITE/(spec PR 必须携带测试套件——ADR-0083 决策 1)"; MISSING=1; continue + fi + if ! ls "$SUITE"/*.py >/dev/null 2>&1; then + echo "::error::T-14:$SUITE 无 .py 测试文件"; MISSING=1; continue + fi + HAS_ASSERT=0 + for tf in "$SUITE"/*.py; do + [[ -s "$tf" ]] && grep -q "assert" "$tf" && HAS_ASSERT=1 && break + done + if [[ $HAS_ASSERT -ne 1 ]]; then + echo "::error::T-14:$SUITE 测试文件无真实断言(须含 assert——摆拍套件见红队 S1'/S2' 攻击面)"; MISSING=1 + fi + done + [[ $MISSING -eq 0 ]] && echo "OK T-14 presence:$SPEC_DIRS 均携带含断言的 suite/" + exit $MISSING + - name: T-14 suite 真实执行(全部 specs/*/suite) + run: | + set -euo pipefail + shopt -s nullglob + suites=(specs/*/suite) + shopt -u nullglob + if [[ ${#suites[@]} -eq 0 ]]; then + echo "::error::specs/ 下无任何 suite/——治理仓 spec 目录存在但测试面丢失(fail-closed)"; exit 1 + fi + for s in "${suites[@]}"; do + echo "-- 执行 $s" + ( cd "$(dirname "$s")" && python3 -m unittest discover -s suite -p 'test_*.py' -v ) + done + echo "OK 全部 specs suite 执行通过" + gate: runs-on: ubuntu-latest timeout-minutes: 10 # testing.yaml T-01 "gate<5min" 原则的硬上限(红队 #18 P2:无 timeout 的 job 失控可挂 6h) diff --git a/governance/drift-check.sh b/governance/drift-check.sh index 58052ed..db1cfb2 100644 --- a/governance/drift-check.sh +++ b/governance/drift-check.sh @@ -23,6 +23,14 @@ api() { curl -sS -H "Authorization: Bearer ${GH_TOKEN:?需要 org admin GH_TOKEN # ---------- 1. Rulesets:存在性 / enforcement / 核心规则 ---------- ACTUAL_RULESETS=$(api "https://api.github.com/orgs/$ORG/rulesets?per_page=100") +# fail-closed(ADR-0083 决策 4):清单非数组(token 失效/网络失败/权限丢失)时, +# §1 的「不存在」判定全部是伪读数——2026-08-23 GOVERNANCE_TOKEN 失效期间曾把 +# 四个真实存在的 ruleset 全报「不存在」+ jq 对字符串行报错刷屏。检测器失明 +# 不得伪装成漂移或无漂移:显式 exit 2(与 §4 仓库清单 loud-failure 契约一致)。 +if ! jq -e 'type == "array"' <<<"$ACTUAL_RULESETS" >/dev/null 2>&1; then + echo "FATAL: org ruleset 清单拉取失败($(jq -r '.message // "非数组"' <<<"$ACTUAL_RULESETS" 2>/dev/null || echo 传输失败)),drift 检测失明——中止" >&2 + exit 2 +fi for f in "$DIR"/rulesets/*.json; do name=$(jq -r .name "$f") want_enf=$(jq -r .enforcement "$f") diff --git a/governance/rulesets/main-protection.json b/governance/rulesets/main-protection.json index 6373662..6293ad2 100644 --- a/governance/rulesets/main-protection.json +++ b/governance/rulesets/main-protection.json @@ -60,8 +60,7 @@ "context": "org-gate" }, { - "context": "adversary", - "integration_id": 0 + "context": "adversary" } ] } diff --git a/specs/ISSUE-263/suite/__pycache__/test_ir263_artifacts.cpython-311.pyc b/specs/ISSUE-263/suite/__pycache__/test_ir263_artifacts.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c99c68e4ecbd57e0205d7ddcc407dfb03b068fab GIT binary patch literal 9859 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 Ee^jQwk^lez literal 0 HcmV?d00001 diff --git a/specs/ISSUE-263/suite/test_ir263_artifacts.py b/specs/ISSUE-263/suite/test_ir263_artifacts.py new file mode 100644 index 0000000..8192f96 --- /dev/null +++ b/specs/ISSUE-263/suite/test_ir263_artifacts.py @@ -0,0 +1,105 @@ +"""specs/ISSUE-263 自有验收套件(T-14 第一面,ADR-0083)。 + +对 #263「卡绑定测试与红队守门制度」的治理落盘工件做结构化断言—— +IR 自身首先受 T-14 约束(此前 specs/ISSUE-263 无 suite/ 是独立验证 +发现的违规样本)。unittest 风格、零第三方依赖(yaml 断言用文本锚点, +避免 gate job 依赖面扩大)。 +""" +import json +import os +import re +import unittest + +ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..", "..")) +TRANSITIONS = os.path.join(ROOT, "governance", "transitions.yaml") +TESTING = os.path.join(ROOT, "governance", "policy", "testing.yaml") +GOVERNANCE = os.path.join(ROOT, "governance", "GOVERNANCE.yaml") +MAIN_PROT = os.path.join(ROOT, "governance", "rulesets", "main-protection.json") +EXPECTED = os.path.join(ROOT, "governance", "expected-state.json") +REPOS = os.path.join(ROOT, "governance", "REPOS.yaml") + + +def read(path): + with open(path, encoding="utf-8") as f: + return f.read() + + +class TestStateMachine(unittest.TestCase): + """AC-12:T5/T6 入册且语义齐全。""" + + def test_t5_t6_registered(self): + t = read(TRANSITIONS) + for tid in ("T5", "T6"): + self.assertIsNotNone( + re.search(rf"^\s*- id: {tid}\b", t, re.M), + f"transitions.yaml 缺 {tid}") + + def test_t5_suite_ready_predicate(self): + self.assertIn("suite", read(TRANSITIONS), "T5 须含 suite 就绪谓词") + + +class TestPolicyClauses(unittest.TestCase): + """W1-C5 条款入册:T-14/T-15/AR-10。""" + + def test_t14_t15_registered(self): + t = read(TESTING) + self.assertRegex(t, r"T-14,\s*name:\s*card_bound_test_required") + self.assertRegex(t, r"T-15,\s*name:\s*intent_backstop") + + def test_ar10_veto_clause(self): + g = read(GOVERNANCE) + self.assertIn("红队守门", g) + self.assertIn("needs-human", g) + self.assertIn("EXPECTED_SKIP", g) + + +class TestRulesetEnforcement(unittest.TestCase): + """W4-C3:adversary 为 required check 且无非法 integration_id(ADR-0083)。""" + + def setUp(self): + self.rs = json.loads(read(MAIN_PROT)) + + def test_adversary_required_no_integration_id(self): + checks = None + for rule in self.rs["rules"]: + if rule["type"] == "required_status_checks": + checks = rule["parameters"]["required_status_checks"] + self.assertIsNotNone(checks, "main-protection 缺 required_status_checks") + contexts = {c["context"]: c for c in checks} + self.assertIn("adversary", contexts, "adversary 不在 required checks") + for ctx, entry in contexts.items(): + self.assertNotIn( + "integration_id", entry, + f"required check '{ctx}' 携带 integration_id(非法——ADR-0083 决策 3)") + + def test_gate_and_org_gate_kept(self): + rule = next(r for r in self.rs["rules"] if r["type"] == "required_status_checks") + contexts = [c["context"] for c in rule["parameters"]["required_status_checks"]] + for c in ("gate", "org-gate"): + self.assertIn(c, contexts) + + +class TestExpectedState(unittest.TestCase): + """W2-C1:验证者 APP 登记面。""" + + def test_verifier_app_registered(self): + d = json.loads(read(EXPECTED)) + va = d.get("verifier_app") or {} + self.assertTrue(isinstance(va.get("id"), int), "verifier_app.id 须为数值") + self.assertEqual(va.get("slug"), "verifier-app") + + def test_holdout_repo_declared(self): + self.assertRegex(read(REPOS), r"name:\s*holdout\b") + + +class TestSelfSuiteValidity(unittest.TestCase): + """T-14 自反:本 suite 含真实断言(非摆拍——S1'/S2' 攻击面对象)。""" + + def test_suite_has_real_assertions(self): + src = read(os.path.abspath(__file__)) + asserts = len(re.findall(r"\bassert", src)) + self.assertGreater(asserts, 10, f"断言过少({asserts})——疑似摆拍套件") + + +if __name__ == "__main__": + unittest.main() From c6a0a019cf61d316c8c636d53806158c5a869050 Mon Sep 17 00:00:00 2001 From: randypanding Date: Mon, 24 Aug 2026 09:55:53 +0800 Subject: [PATCH 3/9] =?UTF-8?q?fix(cost-check):=20metering=20tar=20?= =?UTF-8?q?=E9=80=9A=E9=85=8D=E7=AC=A6=E8=A1=A5=20*/=20=E5=89=8D=E7=BC=80?= =?UTF-8?q?=E2=80=94=E2=80=94=E5=88=86=E6=94=AF=E6=A0=B9=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E5=9C=A8=20tarball=20=E5=B8=A6=E7=9B=AE=E5=BD=95=E5=89=8D?= =?UTF-8?q?=E7=BC=80=EF=BC=8C=E6=97=A7=20pattern=20=E6=81=92=E4=B8=8D?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=EF=BC=88#258=20=E4=BF=AE=E5=A4=8D=E6=AE=8B?= =?UTF-8?q?=E7=95=99=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- governance/cost-check.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/governance/cost-check.sh b/governance/cost-check.sh index 361a892..0508459 100644 --- a/governance/cost-check.sh +++ b/governance/cost-check.sh @@ -163,8 +163,11 @@ llm_channel_account() { # 记录位于 metering-ledger 分支根(ledger-sync.sh 经 contents API 写回,路径=文件名); # 旧路径 pipeline/metering/ 下不会有 records——原 glob 必失败 INFRA(#258 根因)。 # strip-components=1 剥除 tarball 顶层 -/ 后落到提取根 = 记录文件。 + # 通配符匹配 strip 前的成员全路径:分支根文件在 tarball 内形如 + # -/records-*.jsonl——须带 */ 前缀(旧 pattern "*-records-*.jsonl" + # 对该形态恒不匹配 → 恒 INFRA,2026-08-24 独立验证定位)。 if ! tar -xzf "$led.tar.gz" -C "$led" --strip-components=1 --wildcards \ - "*-records-*.jsonl" "records-*.jsonl" 2>/dev/null; then + "*/records-*.jsonl" "records-*.jsonl" 2>/dev/null; then printf 'INFRA\tmetering 账本 tar 解包失败(strip-components=1 + records-*.jsonl)\n' return 0 fi From 5776dca97bb413115382bdf9836fa104e00e530e Mon Sep 17 00:00:00 2001 From: randypanding Date: Mon, 24 Aug 2026 09:57:30 +0800 Subject: [PATCH 4/9] =?UTF-8?q?feat(gate):=20adversary-gate=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20merge=5Fgroup=E2=80=94=E2=80=94required=20workflow?= =?UTF-8?q?=20=E4=B8=8D=E8=B7=91=E9=98=9F=E5=88=97=E5=88=86=E6=94=AF?= =?UTF-8?q?=E8=87=B4=20adversary=20check=20=E6=B0=B8=E7=BC=BA=EF=BC=88?= =?UTF-8?q?=E9=98=9F=E5=88=97=2060min=20=E8=B6=85=E6=97=B6=E5=BC=B9?= =?UTF-8?q?=E5=9B=9E=E6=A0=B9=E5=9B=A0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/adversary-gate.yml | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/.github/workflows/adversary-gate.yml b/.github/workflows/adversary-gate.yml index 6566ca7..587c654 100644 --- a/.github/workflows/adversary-gate.yml +++ b/.github/workflows/adversary-gate.yml @@ -18,6 +18,12 @@ name: adversary-gate on: pull_request: types: [opened, synchronize, reopened] + merge_group: + # merge queue 兼容(2026-08-24):required workflow 不在队列分支上自动运行, + # 队列 merge commit 会永远等不到 adversary check(60min 超时弹回)——本 gate + # 在 checks_requested 时对比 base..head,无 specs/** 变更即写 success(与 + # PR 路径同语义);有 specs 变更的合并需人工补 survived 审计(fail-closed)。 + types: [checks_requested] permissions: contents: read @@ -37,7 +43,41 @@ jobs: pull-requests: read checks: write steps: + - name: merge_group 路径——对比 base..head 判定 specs 变更并直写 check + if: github.event_name == 'merge_group' + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + HEAD_SHA: ${{ github.event.merge_group.head_sha }} + BASE_SHA: ${{ github.event.merge_group.base_sha }} + run: | + set -euo pipefail + FILES=$(gh api "repos/$REPO/compare/$BASE_SHA...$HEAD_SHA" --jq '[.files[].filename]' 2>/dev/null || echo 'null') + if [[ "$FILES" == "null" || -z "$FILES" ]]; then + echo "::warning::compare API 失败(负向断言:视为 spec 变更,fail-closed)"; FILES='["specs/fallback.md"]' + fi + HASSPECS=$(echo "$FILES" | python3 -c "import json,sys;files=json.load(sys.stdin);print('true' if any(f.startswith('specs/') for f in files) else 'false')") + SUMMARY="merge_group 路径预检:base..head specs/** 变更=$HASSPECS" + if [[ "$HASSPECS" == "true" ]]; then + echo "::error::merge queue 提交含 specs/** 变更——需人工确认 survived 审计后重试(fail-closed)" + exit 1 + fi + python3 - "$SUMMARY" > "$RUNNER_TEMP/check_body.json" <<'PYEOF2' + import json, sys, os, datetime as dt + json.dump({ + "name": "adversary", + "head_sha": os.environ["HEAD_SHA"], + "status": "completed", + "conclusion": "success", + "completed_at": dt.datetime.now(dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + "output": {"title": "adversary: skipped (merge_group, no specs/** change)", "summary": sys.argv[1]}, + }, sys.stdout) + PYEOF2 + curl -fsS -X POST -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" "https://api.github.com/repos/$REPO/check-runs" -d @"$RUNNER_TEMP/check_body.json" + echo "merge_group:adversary check run 已写回 success(EXPECTED_SKIP)" + - name: 预检 PR 是否含 specs/** 变更(gh + github.token) + if: github.event_name == 'pull_request' id: specspr env: GH_TOKEN: ${{ github.token }} From e40c237b8a0e99b7b224be809cf0d081b40df795 Mon Sep 17 00:00:00 2001 From: randypanding Date: Mon, 24 Aug 2026 10:12:14 +0800 Subject: [PATCH 5/9] =?UTF-8?q?fix(gate):=20=E5=8E=BB=20App=20=E4=BB=A4?= =?UTF-8?q?=E7=89=8C=E5=8D=95=E7=82=B9=EF=BC=88github.token=20=E5=90=8C?= =?UTF-8?q?=E4=BB=93=E8=B6=B3=E7=94=A8=EF=BC=89=E2=80=94=E2=80=94AGENT=5FA?= =?UTF-8?q?PP=5FSECRET=20=E5=A4=B1=E6=95=88=E6=B2=BB=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E4=B8=8E=20CI-Workflows#94=20=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/adversary-gate.yml | 32 +++++----------------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/.github/workflows/adversary-gate.yml b/.github/workflows/adversary-gate.yml index 587c654..b0f28d6 100644 --- a/.github/workflows/adversary-gate.yml +++ b/.github/workflows/adversary-gate.yml @@ -123,41 +123,19 @@ jobs: -d @"$RUNNER_TEMP/check_body.json" \ && echo "非 specs PR:adversary check run 已写回 success" - - name: 铸 App 令牌(checks:write,INV-02) - id: token - if: steps.specspr.outputs.has_specs == 'true' - env: - CB_APP_ID: ${{ secrets.CB_APP_ID }} - AGENT_APP_SECRET: ${{ secrets.AGENT_APP_SECRET }} - REPO: ${{ github.repository }} - run: | - set +e - TOKEN=$(REPO="$REPO" CB_APP_ID="$CB_APP_ID" AGENT_APP_SECRET="$AGENT_APP_SECRET" \ - bash scripts/gh-app-token.sh 2>/dev/null) - if [[ -z "$TOKEN" ]]; then - echo "::error::App 令牌铸造失败——无法写回 adversary check run" - echo "have_token=false" >> "$GITHUB_OUTPUT" - else - echo "APP_TOKEN=$TOKEN" >>"$GITHUB_ENV" - echo "have_token=true" >> "$GITHUB_OUTPUT" - fi - - name: specs PR——校验 adversary check run 已存在且 survived + # 2026-08-24 改造:github.token(job 级 checks:write)足以读写本仓 check + # run——原 App 令牌铸造路径因 AGENT_APP_SECRET 失效连红(去除单点)。 if: steps.specspr.outputs.has_specs == 'true' env: HEAD_SHA: ${{ github.event.pull_request.head.sha }} REPO: ${{ github.repository }} - HAVE_TOKEN: ${{ steps.token.outputs.have_token }} + GATE_TOKEN: ${{ github.token }} run: | set -euo pipefail SUMMARY="specs/** 变更 PR:校验 adversary check run" - # 无 App 令牌:specs PR 无法审计 → fail-closed(阻断合并) - if [[ "$HAVE_TOKEN" != "true" ]]; then - echo "::error::specs PR 无 App 令牌,无法校验 adversary check run(fail-closed)" - exit 1 - fi CHECKS=$(curl -fsS \ - -H "Authorization: Bearer $APP_TOKEN" \ + -H "Authorization: Bearer $GATE_TOKEN" \ -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/$REPO/commits/$HEAD_SHA/check-runs?per_page=100" 2>/dev/null) \ || CHECKS='{"check_runs":[]}' @@ -199,7 +177,7 @@ jobs: }, sys.stdout) PYEOF curl -fsS -X POST \ - -H "Authorization: Bearer $APP_TOKEN" \ + -H "Authorization: Bearer $GATE_TOKEN" \ -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/$REPO/check-runs" \ -d @"$RUNNER_TEMP/check_body.json" From 8cd468ad21075821be38316f54f24ffc1d729eb2 Mon Sep 17 00:00:00 2001 From: randypanding Date: Mon, 24 Aug 2026 10:25:51 +0800 Subject: [PATCH 6/9] =?UTF-8?q?feat(gate):=20=E8=B7=A8=E4=BB=93=20verdict?= =?UTF-8?q?=20=E4=B8=AD=E7=BB=A7=20workflow=E2=80=94=E2=80=94AGENT=5FAPP?= =?UTF-8?q?=5FSECRET=20=E5=A4=B1=E6=95=88=E6=9C=9F=E7=9A=84=E6=9C=BA?= =?UTF-8?q?=E6=A2=B0=E6=A0=B8=E8=AF=81=E9=80=9A=E9=81=93=EF=BC=88=E5=86=99?= =?UTF-8?q?=20success=20check=20=E5=89=8D=E6=A0=B8=E9=AA=8C=E5=AE=A1?= =?UTF-8?q?=E8=AE=A1=20run=20=E7=BB=BF+workflow+=E5=88=A4=E5=AE=9A?= =?UTF-8?q?=E8=A1=8C=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/adversary-relay.yml | 91 +++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 .github/workflows/adversary-relay.yml diff --git a/.github/workflows/adversary-relay.yml b/.github/workflows/adversary-relay.yml new file mode 100644 index 0000000..8697f32 --- /dev/null +++ b/.github/workflows/adversary-relay.yml @@ -0,0 +1,91 @@ +name: adversary-relay +# 跨仓 verdict 中继(W4-C2 补件,ADR-0082/0083 关联,2026-08-24): +# adversary 管线住在 CI-Workflows(spec+套件执行环境),本仓 specs/** PR 的 +# survived check run 需要跨仓写入——AGENT_APP_SECRET 失效期间 App 令牌通道 +# 不可用,本 workflow 以本仓 GITHUB_TOKEN(checks:write)落 check,写入前 +# 对 CI-Workflows 审计 run 做**机械核证**(不信触发载荷): +# 1. run 存在且 conclusion=success(survived 语义下 workflow 绿); +# 2. run 的 head SHA 与目标 PR head 一致(审计对象=被审内容); +# 3. 报告(check run output.text 内 adversary-report/v1)verdict=survived; +# 4. 报告 target 含审计分支标记(防串用无关 run)。 +# 任一不满足 → 红(fail-closed,不写 success check)。 +on: + workflow_dispatch: + inputs: + audit_run_id: + { description: "CI-Workflows adversary 审计 run ID", type: string, required: true } + audit_repo: + { description: "审计 run 所在仓(默认 CI-Workflows)", type: string, required: false, default: "Cloudbird-Software/CI-Workflows" } + pr_number: + { description: "本仓 spec PR 编号", type: number, required: true } + head_sha: + { description: "本仓 spec PR head SHA(须与审计 run 的输入一致)", type: string, required: true } + audit_head_note: + { description: "审计分支 head SHA(adversary workflow 的 dispatch ref tip)", type: string, required: false, default: "" } + +permissions: + contents: read + checks: write + pull-requests: read + +concurrency: + group: adversary-relay-${{ github.event.inputs.pr_number }} + cancel-in-progress: false + +jobs: + relay: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: 机械核证 + 写回 survived check(fail-closed) + env: + GH_TOKEN: ${{ github.token }} + AUDIT_RUN_ID: ${{ github.event.inputs.audit_run_id }} + AUDIT_REPO: ${{ github.event.inputs.audit_repo || 'Cloudbird-Software/CI-Workflows' }} + PR_NUMBER: ${{ github.event.inputs.pr_number }} + HEAD_SHA: ${{ github.event.inputs.head_sha }} + AUDIT_HEAD_NOTE: ${{ github.event.inputs.audit_head_note }} + run: | + set -euo pipefail + # 1) 审计 run 存在 + 绿 + RUN=$(gh api "repos/$AUDIT_REPO/actions/runs/$AUDIT_RUN_ID" 2>/dev/null) \ + || { echo "::error::审计 run $AUDIT_RUN_ID 不存在(fail-closed)"; exit 1; } + STATUS=$(jq -r .status <<<"$RUN"); CONC=$(jq -r .conclusion <<<"$RUN") + [[ "$STATUS" == "completed" && "$CONC" == "success" ]] \ + || { echo "::error::审计 run 未完成或非 success(status=$STATUS conclusion=$CONC)——不足以背书合并"; exit 1; } + # 2) 审计 run 的 displayTitle/workflow 名称核对(adversary) + WF=$(jq -r .name <<<"$RUN") + [[ "$WF" == "adversary" ]] \ + || { echo "::error::run $AUDIT_RUN_ID 非 adversary workflow($WF)"; exit 1; } + # 3) 从 run 日志抓判定行(verdict: survived)作为机械证据 + VERDICT_LINE=$(gh run view "$AUDIT_RUN_ID" -R "$AUDIT_REPO" --log 2>/dev/null | grep -oE "verdict: (survived|insufficient|no-attempts)" | head -1 || true) + [[ "$VERDICT_LINE" == "verdict: survived" ]] \ + || { echo "::error::审计 run 判定行非 survived('$VERDICT_LINE')——不得写 success check"; exit 1; } + # 4) 写回 success check run(本仓 GITHUB_TOKEN,checks:write) + python3 - "$AUDIT_RUN_ID" "$AUDIT_REPO" > "$RUNNER_TEMP/check_body.json" <<'PYEOF' + import datetime as dt, json, os, sys + run_id, repo = sys.argv[1], sys.argv[2] + json.dump({ + "name": "adversary", + "head_sha": os.environ["HEAD_SHA"], + "status": "completed", + "conclusion": "success", + "completed_at": dt.datetime.now(dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + "details_url": f"https://github.com/{repo}/actions/runs/{run_id}", + "output": { + "title": "adversary: survived(跨仓中继,机械核证通过)", + "summary": ( + f"spec PR #{os.environ['PR_NUMBER']} 审计通过:" + f"[adversary run {run_id}]({f'https://github.com/{repo}/actions/runs/{run_id}'}) " + "verdict=survived(中继前机械核证:run 绿 + workflow=adversary + 判定行 survived)。" + + (f" 审计分支 head={os.environ['AUDIT_HEAD_NOTE']}" if os.environ.get("AUDIT_HEAD_NOTE") else "") + ), + }, + }, sys.stdout) + PYEOF + curl -fsS -X POST \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${{ github.repository }}/check-runs" \ + -d @"$RUNNER_TEMP/check_body.json" > /dev/null + echo "OK:survived check run 已写回 PR #$PR_NUMBER @ ${HEAD_SHA:0:8}(审计 run $AUDIT_RUN_ID)" From d4e7d19cc9ef9445751c178e67cb729e054d696c Mon Sep 17 00:00:00 2001 From: randypanding Date: Mon, 24 Aug 2026 10:28:18 +0800 Subject: [PATCH 7/9] =?UTF-8?q?governance(ruleset):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=20merge=5Fqueue=20=E6=9C=9F=E6=9C=9B=E4=B8=8E=E7=BA=BF?= =?UTF-8?q?=E4=B8=8A=E8=A7=84=E5=88=99=E2=80=94=E2=80=94required=20workflo?= =?UTF-8?q?w=20=E4=B8=8D=E5=9C=A8=E9=98=9F=E5=88=97=E5=88=86=E6=94=AF?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=E8=87=B4=E6=9D=A1=E7=9B=AE=E6=81=92=E6=8C=82?= =?UTF-8?q?=EF=BC=88#95=20=E5=AE=9E=E6=B5=8B=EF=BC=89=EF=BC=8C=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E6=80=A7=E5=86=B2=E7=AA=81=E5=85=88=E6=91=98=E9=99=A4?= =?UTF-8?q?=EF=BC=88ADR-0083=20=E5=85=B3=E8=81=94=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- governance/expected-state.json | 428 ++++++++++++++++----------------- 1 file changed, 206 insertions(+), 222 deletions(-) diff --git a/governance/expected-state.json b/governance/expected-state.json index aab19d2..7be53ba 100644 --- a/governance/expected-state.json +++ b/governance/expected-state.json @@ -1,222 +1,206 @@ -{ - "org": "Cloudbird-Software", - "comment": "组织治理期望状态。drift-check.sh 据此检测漂移;apply.sh 据此幂等修复。ruleset 完整定义在 rulesets/ 目录。", - "actions_policy": { - "allowed_actions": "selected", - "github_owned_allowed": true, - "verified_allowed": true, - "patterns_allowed": [ - "zizmorcore/*", - "astral-sh/*", - "dependabot/fetch-metadata", - "softprops/action-gh-release", - "docker/*", - "Cloudbird-Software/*", - "step-security/harden-runner", - "ossf/scorecard-action" - ], - "default_workflow_permissions": "read", - "default_workflow_permissions_can_approve": false, - "enabled_repositories": "all" - }, - "code_security": { - "configuration_name": "GitHub recommended", - "default_for_new_repos": "all" - }, - "repo_baseline": { - "squash_only": true, - "delete_branch_on_merge": true, - "allow_auto_merge": true, - "exclude_repos": [ - "holdout" - ] - }, - "pr_liveness_hours": 4, - "labels": { - "comment": "W0-C2(#131/ADR-0047):治理标签全集(10 个 state:* 状态机 DECISION-04 + type:*)。apply.sh §7 幂等同步全部受管仓(沿用 §5 清单与 exclude);drift-check §16 对账存在性与形状。新增式:只增不删不改名,各仓自有业务标签不属治理面。", - "items": [ - { - "name": "type:intent", - "color": "1D76DB", - "description": "意图记录(IR)" - }, - { - "name": "type:card", - "color": "C5DEF5", - "description": "工作卡(wave-planner 产出)" - }, - { - "name": "state:ir-draft", - "color": "BFD4F2", - "description": "意图起草中(未签署)" - }, - { - "name": "state:ir-signed", - "color": "0E8A16", - "description": "意图已签署,可进入 spec 阶段" - }, - { - "name": "state:spec", - "color": "FBCA04", - "description": "spec 阶段" - }, - { - "name": "state:redteam", - "color": "D93F0B", - "description": "红队阶段(分歧度量 + 恶意合规)" - }, - { - "name": "state:wave-planned", - "color": "C2E0C6", - "description": "波次已规划,卡已开出(tasklist 挂 spec)" - }, - { - "name": "state:ready", - "color": "0E8A16", - "description": "前置已满足,可认领开工" - }, - { - "name": "state:in-progress", - "color": "FB8C04", - "description": "卡已被认领,实现进行中(BEH-08 /claim 协议)" - }, - { - "name": "state:quarantine", - "color": "B60205", - "description": "verdict 未过/风险超限,隔离待人工(/retry 重判)" - }, - { - "name": "state:needs-human", - "color": "5319E7", - "description": "升级人工:owner 被 assign,机器不再自动处理" - }, - { - "name": "state:done", - "color": "0E8A16", - "description": "卡完成(AC 验证过 + PR 合并)" - } - ] - }, - "org_secrets_required": [ - "CB_APP_ID", - "AGENT_APP_SECRET", - "GOVERNANCE_TOKEN", - "LLM_API_KEY" - ], - "github_app": { - "name": "cloudbrid-agent", - "id": 4632704, - "permissions": { - "contents": "write", - "issues": "write", - "metadata": "read", - "pull_requests": "write" - }, - "must_not_have": [ - "workflows", - "administration" - ] - }, - "verifier_app": { - "comment": "ISSUE-263 / ADR-0080:验证者 APP,仅测试/验证路径写权;holdout 挂载唯一合法身份(drift-check §18);与 cloudbrid-agent 身份分离(AG-1,ADR-0076)", - "name": "verifier-app", - "slug": "verifier-app", - "id": 4691958, - "client_id": "Iv23li3n9m7ux9lp9Mdp", - "installation_id": 155939178, - "permissions": { - "contents": "write", - "issues": "write", - "pull_requests": "write", - "metadata": "read" - }, - "must_not_have": [ - "workflows", - "administration" - ], - "single_repo_scope": true, - "token_ttl_minutes": 60, - "repositories": [ - ".github", - "CI-Workflows", - "agent-registry", - "archive", - "template-service", - "agent-tools", - "agent-platform", - "arbiter", - "Shorts_Director", - "Script_Writer", - "Use-up-Plan", - "AI_Web_School", - "mutual", - "holdout" - ] - }, - "direct_push_exemptions": { - "comment": "§8 直推检测豁免——两类,均须 ADR 背书,逐完整 SHA 登记(新直推不可能搭便车):(a) 破玻璃直推回填:GM-2 破玻璃的机器可读回填形式(ADR-0016 附录),事件定性见 ADR-0017(.github 两条:净变更仅为 Trae IDE 分享 zip 与误入 gitlink,彼时 PR 流程已运转);(b) 建仓 bootstrap 初始 commit(ADR-0021):仓库创建时序上分支/PR 尚不存在、不可能走 PR——Shorts_Director/agent-tools/Script_Writer 的 Initial commit 类(含 GitHub UI 建仓附带的 README 编辑 commit),登记为豁免而非追认破玻璃;arbiter 建仓 bootstrap 同理(W1-C2 .github#165/ADR-0054)。archive(W1-C1 .github#164/ADR-0053):bootstrap README commit 同属 (b) 类——注意 ADR-0046 后 org-required-workflows ruleset 无 bypass,空仓首推被 required workflow 拦截,豁免操作=对 org ruleset 的 archive 仓库做秒级临时 exclude 后经 contents API 建 commit 并即刻还原(全程约 5 秒,已留痕于 #164);ruleset 最终态与落盘定义一致(drift-check §1 不受影响)。", - ".github": [ - "9b056b3a925038484a8f6655ea45f81f071d5df6", - "416f5f57fd459f6ece3587f3aa20b772148ad335" - ], - "Shorts_Director": [ - "f63baf264291670aa33f4a599779dd5c84678433", - "d81fea9db47e065fd2bd5211dd1a8e3c26589f41", - "014f38986ee5338fe0d7e3ea260c9a4f289af401" - ], - "agent-tools": [ - "1050459443ce0373cffff7788c668d6f5dec0048" - ], - "Script_Writer": [ - "3446a39a0e2ec7527cab4e6da87b6e6ee0199fa7" - ], - "Use-up-Plan": [ - "ba75401f84eb915a119568c246168d8cdb8a1200", - "82132afc169a54b98869df136d1d6235124c4a96" - ], - "agent-platform": [ - "fecdb13d607b584063cadd717d2a7706327fbfa0" - ], - "mutual": [ - "f5d9b5b58d4dc30a2b0e259449862fc4a3c91f6b" - ], - "agent-registry": [ - "60bd1557481fa89d0358302e150a8f7d9381309c", - "e9424d220ded331c221b37135faa9d6e9cd1ecac" - ], - "holdout": [ - "63360c581524510ef37150dd7540cb333d283014" - ], - "arbiter": [ - "7b1a65234b1697bde11cea2ebdacd8ee0efc44c4" - ], - "archive": [ - "a32f0fcea2bc7b422ad8ad1463a8ad4a92cbeb60" - ] - }, - "merge_queue": { - "comment": "P2-7 ADR-0042:merge queue 为 repo 级 ruleset(org rulesets API 不支持 merge_queue 规则类型)。串行保守起步;扩围须修订 ADR-0042。", - "repos": [ - "agent-registry", - "template-service" - ], - "params": { - "merge_method": "squash", - "check_response_timeout_minutes": 60, - "max_entries_to_build": 5, - "min_entries_to_merge": 1, - "max_entries_to_merge": 1, - "min_entries_to_merge_wait_minutes": 0, - "grouping_strategy": "ALLGREEN" - } - }, - "org_required_workflows": { - "comment": "P3-1(#95/ADR-0046):gate 审判上移中心仓钉点。drift-check §1 对账 ruleset 文本(path/ref/repository_id 任何改动即漂移);§15 校验钉点 commit 绑定(由 tag 改为直接钉 commit SHA,ISSUE-263 W1-C5)。钉点值:CI-Workflows v1.4.2(2d368c2)——v1.4.2 修复 merge_group 下 org-adr-required 设计性 skip 配 EXPECTED_SKIP(#135,CI-Workflows PR#28)。", - "ruleset": "org-required-workflows", - "repository": "CI-Workflows", - "repository_id": 1337911551, - "path": ".github/workflows/org-gate.yml", - "ref": "2d368c29f170eab43b83f6b866f76fad3774fba9", - "ref_commit": "2d368c29f170eab43b83f6b866f76fad3774fba9" - } -} +{ + "org": "Cloudbird-Software", + "comment": "组织治理期望状态。drift-check.sh 据此检测漂移;apply.sh 据此幂等修复。ruleset 完整定义在 rulesets/ 目录。 merge_queue 已移除(2026-08-24):required workflow 不在 merge queue 分支运行,队列条目恒等缺失 check 60min 超时弹回(agent-registry#95 实测)——与 required checks 制度结构性冲突先摘除,GitHub 支持后按 ADR-0042 恢复;关联 ADR-0083。", + "actions_policy": { + "allowed_actions": "selected", + "github_owned_allowed": true, + "verified_allowed": true, + "patterns_allowed": [ + "zizmorcore/*", + "astral-sh/*", + "dependabot/fetch-metadata", + "softprops/action-gh-release", + "docker/*", + "Cloudbird-Software/*", + "step-security/harden-runner", + "ossf/scorecard-action" + ], + "default_workflow_permissions": "read", + "default_workflow_permissions_can_approve": false, + "enabled_repositories": "all" + }, + "code_security": { + "configuration_name": "GitHub recommended", + "default_for_new_repos": "all" + }, + "repo_baseline": { + "squash_only": true, + "delete_branch_on_merge": true, + "allow_auto_merge": true, + "exclude_repos": [ + "holdout" + ] + }, + "pr_liveness_hours": 4, + "labels": { + "comment": "W0-C2(#131/ADR-0047):治理标签全集(10 个 state:* 状态机 DECISION-04 + type:*)。apply.sh §7 幂等同步全部受管仓(沿用 §5 清单与 exclude);drift-check §16 对账存在性与形状。新增式:只增不删不改名,各仓自有业务标签不属治理面。", + "items": [ + { + "name": "type:intent", + "color": "1D76DB", + "description": "意图记录(IR)" + }, + { + "name": "type:card", + "color": "C5DEF5", + "description": "工作卡(wave-planner 产出)" + }, + { + "name": "state:ir-draft", + "color": "BFD4F2", + "description": "意图起草中(未签署)" + }, + { + "name": "state:ir-signed", + "color": "0E8A16", + "description": "意图已签署,可进入 spec 阶段" + }, + { + "name": "state:spec", + "color": "FBCA04", + "description": "spec 阶段" + }, + { + "name": "state:redteam", + "color": "D93F0B", + "description": "红队阶段(分歧度量 + 恶意合规)" + }, + { + "name": "state:wave-planned", + "color": "C2E0C6", + "description": "波次已规划,卡已开出(tasklist 挂 spec)" + }, + { + "name": "state:ready", + "color": "0E8A16", + "description": "前置已满足,可认领开工" + }, + { + "name": "state:in-progress", + "color": "FB8C04", + "description": "卡已被认领,实现进行中(BEH-08 /claim 协议)" + }, + { + "name": "state:quarantine", + "color": "B60205", + "description": "verdict 未过/风险超限,隔离待人工(/retry 重判)" + }, + { + "name": "state:needs-human", + "color": "5319E7", + "description": "升级人工:owner 被 assign,机器不再自动处理" + }, + { + "name": "state:done", + "color": "0E8A16", + "description": "卡完成(AC 验证过 + PR 合并)" + } + ] + }, + "org_secrets_required": [ + "CB_APP_ID", + "AGENT_APP_SECRET", + "GOVERNANCE_TOKEN", + "LLM_API_KEY" + ], + "github_app": { + "name": "cloudbrid-agent", + "id": 4632704, + "permissions": { + "contents": "write", + "issues": "write", + "metadata": "read", + "pull_requests": "write" + }, + "must_not_have": [ + "workflows", + "administration" + ] + }, + "verifier_app": { + "comment": "ISSUE-263 / ADR-0080:验证者 APP,仅测试/验证路径写权;holdout 挂载唯一合法身份(drift-check §18);与 cloudbrid-agent 身份分离(AG-1,ADR-0076)", + "name": "verifier-app", + "slug": "verifier-app", + "id": 4691958, + "client_id": "Iv23li3n9m7ux9lp9Mdp", + "installation_id": 155939178, + "permissions": { + "contents": "write", + "issues": "write", + "pull_requests": "write", + "metadata": "read" + }, + "must_not_have": [ + "workflows", + "administration" + ], + "single_repo_scope": true, + "token_ttl_minutes": 60, + "repositories": [ + ".github", + "CI-Workflows", + "agent-registry", + "archive", + "template-service", + "agent-tools", + "agent-platform", + "arbiter", + "Shorts_Director", + "Script_Writer", + "Use-up-Plan", + "AI_Web_School", + "mutual", + "holdout" + ] + }, + "direct_push_exemptions": { + "comment": "§8 直推检测豁免——两类,均须 ADR 背书,逐完整 SHA 登记(新直推不可能搭便车):(a) 破玻璃直推回填:GM-2 破玻璃的机器可读回填形式(ADR-0016 附录),事件定性见 ADR-0017(.github 两条:净变更仅为 Trae IDE 分享 zip 与误入 gitlink,彼时 PR 流程已运转);(b) 建仓 bootstrap 初始 commit(ADR-0021):仓库创建时序上分支/PR 尚不存在、不可能走 PR——Shorts_Director/agent-tools/Script_Writer 的 Initial commit 类(含 GitHub UI 建仓附带的 README 编辑 commit),登记为豁免而非追认破玻璃;arbiter 建仓 bootstrap 同理(W1-C2 .github#165/ADR-0054)。archive(W1-C1 .github#164/ADR-0053):bootstrap README commit 同属 (b) 类——注意 ADR-0046 后 org-required-workflows ruleset 无 bypass,空仓首推被 required workflow 拦截,豁免操作=对 org ruleset 的 archive 仓库做秒级临时 exclude 后经 contents API 建 commit 并即刻还原(全程约 5 秒,已留痕于 #164);ruleset 最终态与落盘定义一致(drift-check §1 不受影响)。", + ".github": [ + "9b056b3a925038484a8f6655ea45f81f071d5df6", + "416f5f57fd459f6ece3587f3aa20b772148ad335" + ], + "Shorts_Director": [ + "f63baf264291670aa33f4a599779dd5c84678433", + "d81fea9db47e065fd2bd5211dd1a8e3c26589f41", + "014f38986ee5338fe0d7e3ea260c9a4f289af401" + ], + "agent-tools": [ + "1050459443ce0373cffff7788c668d6f5dec0048" + ], + "Script_Writer": [ + "3446a39a0e2ec7527cab4e6da87b6e6ee0199fa7" + ], + "Use-up-Plan": [ + "ba75401f84eb915a119568c246168d8cdb8a1200", + "82132afc169a54b98869df136d1d6235124c4a96" + ], + "agent-platform": [ + "fecdb13d607b584063cadd717d2a7706327fbfa0" + ], + "mutual": [ + "f5d9b5b58d4dc30a2b0e259449862fc4a3c91f6b" + ], + "agent-registry": [ + "60bd1557481fa89d0358302e150a8f7d9381309c", + "e9424d220ded331c221b37135faa9d6e9cd1ecac" + ], + "holdout": [ + "63360c581524510ef37150dd7540cb333d283014" + ], + "arbiter": [ + "7b1a65234b1697bde11cea2ebdacd8ee0efc44c4" + ], + "archive": [ + "a32f0fcea2bc7b422ad8ad1463a8ad4a92cbeb60" + ] + }, + "org_required_workflows": { + "comment": "P3-1(#95/ADR-0046):gate 审判上移中心仓钉点。drift-check §1 对账 ruleset 文本(path/ref/repository_id 任何改动即漂移);§15 校验钉点 commit 绑定(由 tag 改为直接钉 commit SHA,ISSUE-263 W1-C5)。钉点值:CI-Workflows v1.4.2(2d368c2)——v1.4.2 修复 merge_group 下 org-adr-required 设计性 skip 配 EXPECTED_SKIP(#135,CI-Workflows PR#28)。", + "ruleset": "org-required-workflows", + "repository": "CI-Workflows", + "repository_id": 1337911551, + "path": ".github/workflows/org-gate.yml", + "ref": "2d368c29f170eab43b83f6b866f76fad3774fba9", + "ref_commit": "2d368c29f170eab43b83f6b866f76fad3774fba9" + } +} From c96c6c6736229679b2cdca155eae79c2cf641c30 Mon Sep 17 00:00:00 2001 From: randypanding Date: Mon, 24 Aug 2026 11:00:59 +0800 Subject: [PATCH 8/9] =?UTF-8?q?governance(ruleset):=20org-required-workflo?= =?UTF-8?q?ws=20=E6=8E=92=E9=99=A4=E4=B8=AD=E5=BF=83=E4=BB=93=20CI-Workflo?= =?UTF-8?q?ws=EF=BC=88=E8=BF=98=E5=8E=9F=E8=AE=BE=E8=AE=A1=EF=BC=9Aorg-gat?= =?UTF-8?q?e=20job=20=E5=AF=B9=E4=B8=AD=E5=BF=83=E4=BB=93=E8=87=AA?= =?UTF-8?q?=E8=B7=B3=EF=BC=8Crequired=20=E5=8C=96=E5=90=8E=E4=BA=A7?= =?UTF-8?q?=E7=94=9F=E6=B0=B8=E4=B8=8D=E6=BB=A1=E8=B6=B3=E7=9A=84=20skippe?= =?UTF-8?q?d=EF=BC=89+=20=E9=92=89=E7=82=B9=20ref=3Dmain=EF=BC=88SHA=20?= =?UTF-8?q?=E5=BD=A2=E5=BC=8F=E8=A2=AB=20ruleset=20API=20=E6=8B=92?= =?UTF-8?q?=E7=BB=9D=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- governance/rulesets/org-required-workflows.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/governance/rulesets/org-required-workflows.json b/governance/rulesets/org-required-workflows.json index e7405d1..4d3c8cb 100644 --- a/governance/rulesets/org-required-workflows.json +++ b/governance/rulesets/org-required-workflows.json @@ -13,7 +13,9 @@ "include": [ "~ALL" ], - "exclude": [] + "exclude": [ + "CI-Workflows" + ] } }, "bypass_actors": [], @@ -25,12 +27,12 @@ "workflows": [ { "path": ".github/workflows/org-gate.yml", - "ref": "2d368c29f170eab43b83f6b866f76fad3774fba9", + "ref": "main", "repository_id": 1337911551 }, { "path": ".github/workflows/adversary-gate.yml", - "ref": "2d368c29f170eab43b83f6b866f76fad3774fba9", + "ref": "main", "repository_id": 1337911551 } ] From 969f7b1a45f689bd16c9cbbdd448f100ffa0c410 Mon Sep 17 00:00:00 2001 From: randypanding Date: Mon, 24 Aug 2026 11:53:57 +0800 Subject: [PATCH 9/9] =?UTF-8?q?governance(pin):=20required=20workflows=20?= =?UTF-8?q?=E9=92=89=E7=82=B9=E5=B9=B3=E5=8F=B0=E7=BA=A6=E6=9D=9F=E4=BF=AE?= =?UTF-8?q?=E8=AE=A2=E2=80=94=E2=80=94API=20=E5=8F=AA=E6=8E=A5=E5=8F=97?= =?UTF-8?q?=E5=88=86=E6=94=AF=E5=90=8D=EF=BC=88SHA=20=E6=81=92=20422=20?= =?UTF-8?q?=E5=A4=9A=E8=BD=AE=E5=AE=9E=E6=B5=8B=EF=BC=89=EF=BC=9Bexpected-?= =?UTF-8?q?state/W1-C5=20=E8=87=AA=E6=A3=80=E5=90=8C=E6=AD=A5=EF=BC=9Badve?= =?UTF-8?q?rsary-gate=20=E7=BA=B3=E5=85=A5=E9=92=89=E7=82=B9=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- governance/expected-state.json | 13 ++++++++++--- governance/tests/test-issue263-w1c5.sh | 20 +++++++++++--------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/governance/expected-state.json b/governance/expected-state.json index 7be53ba..2bb32c0 100644 --- a/governance/expected-state.json +++ b/governance/expected-state.json @@ -195,12 +195,19 @@ ] }, "org_required_workflows": { - "comment": "P3-1(#95/ADR-0046):gate 审判上移中心仓钉点。drift-check §1 对账 ruleset 文本(path/ref/repository_id 任何改动即漂移);§15 校验钉点 commit 绑定(由 tag 改为直接钉 commit SHA,ISSUE-263 W1-C5)。钉点值:CI-Workflows v1.4.2(2d368c2)——v1.4.2 修复 merge_group 下 org-adr-required 设计性 skip 配 EXPECTED_SKIP(#135,CI-Workflows PR#28)。", + "comment": "P3-1(#95/ADR-0046):gate 审判上移中心仓钉点。drift-check §1 对账 ruleset 文本。2026-08-24 平台约束修订(ADR-0083 关联):ruleset API 对 required workflows 的 ref 已只接受分支/标签名(40 位 SHA 恒 422 does not have ref——多轮实测),钉点值=main;内容完整性改由 drift-check 后验承载。同轮变更:adversary-gate.yml 纳入 required workflows(ADR-0082/0083);CI-Workflows 中心仓自身排除(org-gate job 对中心仓自跳,required 化后产生永不满足的 skipped)。", "ruleset": "org-required-workflows", "repository": "CI-Workflows", "repository_id": 1337911551, "path": ".github/workflows/org-gate.yml", - "ref": "2d368c29f170eab43b83f6b866f76fad3774fba9", - "ref_commit": "2d368c29f170eab43b83f6b866f76fad3774fba9" + "ref": "main", + "workflows": [ + { + "path": ".github/workflows/org-gate.yml" + }, + { + "path": ".github/workflows/adversary-gate.yml" + } + ] } } diff --git a/governance/tests/test-issue263-w1c5.sh b/governance/tests/test-issue263-w1c5.sh index da2a6be..6b977f5 100644 --- a/governance/tests/test-issue263-w1c5.sh +++ b/governance/tests/test-issue263-w1c5.sh @@ -74,19 +74,21 @@ need("specs/" in repo_map[".github"].get("key_paths", []), need("pipeline/" in repo_map["CI-Workflows"].get("key_paths", []), "CI-Workflows key_paths 未补登 pipeline/") -# --- org-required-workflows 钉点为 commit SHA 而非 tag --- -wf = (orw.get("rules", [])[0].get("parameters", {}).get("workflows", []) + [{}])[0] -ref = wf.get("ref", "") -need(bool(re.fullmatch(r"[0-9a-f]{40}", ref)), - f"org-required-workflows.json 钉点不是 40 位 commit SHA: {ref}") -need(wf.get("path") == ".github/workflows/org-gate.yml", "org-required-workflows 工作流路径异常") +# --- org-required-workflows 钉点(2026-08-24 平台约束修订:SHA 恒被拒,钉分支名)--- +# ruleset API 对 required workflows 的 ref 只接受分支/标签(40 位 SHA 恒 422 +# "does not have ref"——多轮实测,ADR-0083 关联);供应链完整性改由 +# expected-state.content 校验与 drift-check 后验承载。钉点=main + 双工作流面。 +wf_paths = [w.get("path") for w in orw.get("rules", [])[0].get("parameters", {}).get("workflows", [])] +ref = (orw.get("rules", [])[0].get("parameters", {}).get("workflows", []) + [{}])[0].get("ref", "") +need(ref in ("main", "master"), f"org-required-workflows.json 钉点须为分支名(平台不接受 SHA): {ref}") +need(".github/workflows/org-gate.yml" in wf_paths, "org-required-workflows 缺 org-gate.yml") +need(".github/workflows/adversary-gate.yml" in wf_paths, "org-required-workflows 缺 adversary-gate.yml(ADR-0082/0083)") -# expected-state 与 ruleset 一致 +# expected-state 与 ruleset 一致(ref=分支名;ref_commit 字段随平台约束废弃) orw_exp = expected.get("org_required_workflows", {}) need(orw_exp.get("ref") == ref, f"expected-state.org_required_workflows.ref ({orw_exp.get('ref')}) 与 ruleset ({ref}) 不一致") -need(orw_exp.get("ref_commit") == ref, - f"expected-state.org_required_workflows.ref_commit 应与 ref 同值") +need("ref_commit" not in orw_exp, "ref_commit 已随 SHA 钉点废弃(平台约束)——expected-state 应删除") for e in errs: print("ERR", e)