From f0fef8fedbb09bee46b121aa8d3da00f1c7935fe Mon Sep 17 00:00:00 2001 From: randypanding Date: Mon, 31 Aug 2026 14:08:15 +0000 Subject: [PATCH] =?UTF-8?q?fix(gate):=20adversary-gate=20specs=20=E9=82=BB?= =?UTF-8?q?=E6=8E=A5=E7=B2=BE=E5=8C=96=E2=80=94=E2=80=94=E5=8F=AF=E5=AE=A1?= =?UTF-8?q?=E8=AE=A1=E6=80=A7=E5=88=A4=E5=88=AB=EF=BC=88AC-14=20=E7=A1=AE?= =?UTF-8?q?=E5=AE=9A=E6=80=A7=E6=B4=BE=E7=94=9F=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit specs/** 命中只统计可审计 spec 体:变更路径所属 specs// 在 PR head 上存在 spec.md(adversary 目标契约判别面)。无 spec.md 的 specs/ 目录 (test-freeze 的 MANIFEST 哈希账本等派生资产)→ EXPECTED_SKIP 写 success check 放行——其完整性由自身哈希链执法,红队无面可攻。 - 判定由 diff 路径集 + head 树确定性派生(禁人工打标,AC-14 语义收敛) - specs/ 根直挂文件 fail-closed 视为可审计(判别面外不放行) - API 失败负向断言不变;spec 体变更路径与 merge_group fail-closed 均不变 触发实例:AI_Web_School#169(specs/test-freeze/MANIFEST.sha256 重签—— 无 spec.md 审计体,原谓词误伤恒红且无解)。 --- .github/workflows/adversary-gate.yml | 62 +++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/.github/workflows/adversary-gate.yml b/.github/workflows/adversary-gate.yml index b0f28d6..c3f0359 100644 --- a/.github/workflows/adversary-gate.yml +++ b/.github/workflows/adversary-gate.yml @@ -77,11 +77,19 @@ jobs: echo "merge_group:adversary check run 已写回 success(EXPECTED_SKIP)" - name: 预检 PR 是否含 specs/** 变更(gh + github.token) + # 2026-08-31 精化(AC-14 豁免谓词确定性派生):specs/** 命中只统计 + # **可审计 spec 体**——变更路径所属 specs// 在 PR head 上存在 + # spec.md(adversary 目标契约 specs//{spec.md,suite/,run-suite.sh} + # 的判别面)。specs/ 下无 spec.md 的目录(如 test-freeze 的 MANIFEST + # 派生哈希账本)是机器可校验资产而非红队审计对象——其完整性由 + # 自身哈希链/签名执法,红队无面可攻。判定仍由 diff 路径集 + head 树 + # 确定性派生(禁人工打标);API 失败负向断言不变(fail-closed)。 if: github.event_name == 'pull_request' id: specspr env: GH_TOKEN: ${{ github.token }} PR_API: "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}" + HEAD_SHA: ${{ github.event.pull_request.head.sha }} run: | set -euo pipefail set +e @@ -93,10 +101,60 @@ jobs: echo "has_specs=true" >> "$GITHUB_OUTPUT" echo "::warning::取 PR files 失败(负向断言:视为 spec 变更)" else - 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')") - echo "has_specs=$HASSPECS" >> "$GITHUB_OUTPUT" + SPECS_HITS=$(echo "$FILES" | python3 -c "import json,sys;files=json.load(sys.stdin);print('\n'.join(f for f in files if f.startswith('specs/')))") + if [[ -z "$SPECS_HITS" ]]; then + echo "has_specs=false" >> "$GITHUB_OUTPUT" + else + # 逐变更 specs 目录核验可审计性(head 树上 specs//spec.md 存在) + AUDITABLE=0; ADJACENT_DIRS="" + for d in $(echo "$SPECS_HITS" | sed 's|^specs/||' | cut -d/ -f1 | sort -u); do + [[ -n "$d" ]] || continue + if gh api "repos/${{ github.repository }}/contents/specs/$d/spec.md?ref=$HEAD_SHA" >/dev/null 2>&1; then + AUDITABLE=1 + else + ADJACENT_DIRS="$ADJACENT_DIRS $d" + fi + done + # specs/ 根下直挂文件(无目录层)→ fail-closed 视为可审计(红队判别面外不放行) + if echo "$SPECS_HITS" | grep -qv '^specs/[^/]*/'; then AUDITABLE=1; fi + if [[ $AUDITABLE -eq 1 ]]; then + echo "has_specs=true" >> "$GITHUB_OUTPUT" + else + echo "has_specs=adjacent" >> "$GITHUB_OUTPUT" + echo "adjacent_dirs=$(echo $ADJACENT_DIRS)" >> "$GITHUB_OUTPUT" + echo "specs 邻接变更(无可审计 spec 体):$ADJACENT_DIRS" + fi + fi fi + - name: specs 邻接变更(无 spec.md 审计体)——写 success check run 放行 + # EXPECTED_SKIP(AC-14):specs/** 命中但全部属无 spec.md 的目录 + # (MANIFEST/README 类派生资产)——确定性派生豁免,非人工打标。 + if: steps.specspr.outputs.has_specs == 'adjacent' + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + SUMMARY="specs/** 邻接变更但无可审计 spec 体(${{ steps.specspr.outputs.adjacent_dirs }} 无 spec.md,adversary 目标契约不成立):EXPECTED_SKIP=True(AC-14 确定性派生豁免——哈希账本/MANIFEST 类资产由自身哈希链执法)" + python3 - "$SUMMARY" > "$RUNNER_TEMP/check_body.json" <<'PYEOF3' + import json, sys, datetime as dt + summary = sys.argv[1] + json.dump({ + "name": "adversary", + "head_sha": "${{ github.event.pull_request.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 (specs-adjacent, no auditable spec body)", "summary": summary}, + }, sys.stdout) + PYEOF3 + 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" \ + && echo "specs 邻接变更:adversary check run 已写回 success(EXPECTED_SKIP)" + - name: 非 specs PR——写 success check run 并放行(github.token) if: steps.specspr.outputs.has_specs == 'false' env: