Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ __pycache__/
.archive-w1d1/
.ciw/
env-defs/
.scratch/
15 changes: 10 additions & 5 deletions governance/evidence-query.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
# 源 3 butler Cloudbird-Software/.github @ butler-ledger governance/butler/shadow-evidence.jsonl
# 源 4 elevation Cloudbird-Software/.github @ elevation-ledger governance/elevation/shadow-evidence.jsonl
# (W2-C4 JIT 提权裁决/收回记录——subject 可查询即 AC-9c 锚点)
# 源 5 tickets Cloudbird-Software/cnb-bridge @ tickets-ledger tickets.jsonl
# (W2-C1 内网调度器短票据 grant/revoke——AC-5b 统一账本;Go 发射器产出,
# 链形态与 evidence_shadow.py 逐字节兼容,金向量锚定)
#
# 用法:
# bash governance/evidence-query.sh [--card owner/repo#n] [--json] # --json=汇总行也走 stdout
Expand Down Expand Up @@ -70,20 +73,22 @@ fi
DRILL_OK=0; fetch_file "Cloudbird-Software/.github" "drill-ledger" "governance/drill/shadow-evidence.jsonl" "$TMP/drill.jsonl" && DRILL_OK=1 || [[ $? -eq 1 ]] || exit 2
BUTLER_OK=0; fetch_file "Cloudbird-Software/.github" "butler-ledger" "governance/butler/shadow-evidence.jsonl" "$TMP/butler.jsonl" && BUTLER_OK=1 || [[ $? -eq 1 ]] || exit 2
ELEV_OK=0; fetch_file "Cloudbird-Software/.github" "elevation-ledger" "governance/elevation/shadow-evidence.jsonl" "$TMP/elev.jsonl" && ELEV_OK=1 || [[ $? -eq 1 ]] || exit 2
TICKET_OK=0; fetch_file "Cloudbird-Software/cnb-bridge" "tickets-ledger" "tickets.jsonl" "$TMP/tickets.jsonl" && TICKET_OK=1 || [[ $? -eq 1 ]] || exit 2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. tickets fetch bypasses app identity 📘 Rule violation ⛨ Security

The added tickets-ledger fetch executes a GitHub API operation through gh while the script accepts
an arbitrary ambient GH_TOKEN, explicitly including PATs, rather than obtaining a GitHub App token
through scripts/ghcb. This extends the non-standard credential path to
Cloudbird-Software/cnb-bridge.
Agent Prompt
## Issue description
The new `tickets` source uses the script's caller-provided `GH_TOKEN`/PAT path instead of the repository-standard GitHub App identity.

## Issue Context
Compliance rule 2778539 requires scripts performing GitHub API calls to obtain tokens through `scripts/ghcb` or `scripts/gh-app-token.sh`. Because the query reads multiple private repositories, mint and use an appropriately repository-scoped App token for each fetch rather than accepting an arbitrary PAT.

## Fix Focus Areas
- governance/evidence-query.sh[18-40]
- governance/evidence-query.sh[73-76]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread
qodo-code-review[bot] marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. 权限失败伪装源缺席 🐞 Bug ⛨ Security

新增 cnb-bridge 仓未纳入脚本仍声明的“两仓可读”令牌契约,而 fetch_file 把所有 HTTP 404 都视为合法源缺席。GitHub 对无权访问的私有资源也返回
404,因此符合现有文档的细粒度 PAT 可让查询以 rc=0 成功却完全漏掉 tickets。
Agent Prompt
## Issue description
无法区分 tickets 文件缺席与调用者无权读取 cnb-bridge,权限错误会被静默放行。

## Issue Context
先对三个预期仓执行可见性/权限探测并 fail closed,再仅把已确认可见仓中的账本路径或 ref 404 当作过渡期缺席;同时更新 GH_TOKEN 契约和错误文案。

## Fix Focus Areas
- governance/evidence-query.sh[18-18]
- governance/evidence-query.sh[33-33]
- governance/evidence-query.sh[38-55]
- governance/evidence-query.sh[73-76]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

4. 大账本被静默清空 🐞 Bug ☼ Reliability

tickets 是持续追加的单一 tickets.jsonl,但新增拉取沿用默认 Contents API JSON 响应;文件超过 1 MB 后 GitHub 会返回空 contentencoding: none。当前解码会写出空文件,随后查询跳过它并返回成功,造成整个 tickets 源静默消失。
Agent Prompt
## Issue description
默认 Contents API 响应无法承载超过 1 MB 的持续增长 tickets 账本,查询会把其误读为空源。

## Issue Context
使用 GitHub raw media type 直接下载账本内容,检查响应形态,并为超过 1 MB 的 fixture/响应增加回归测试。

## Fix Focus Areas
- governance/evidence-query.sh[38-47]
- governance/evidence-query.sh[73-76]
- governance/evidence-query.sh[93-99]
- governance/tests/test-evidence-shadow.sh[152-215]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


# ---- 逐源验链 + 归并输出(链断=exit 3:不可信数据不出结果) ----
export CARD_FILTER JSON_ONLY DRILL_OK BUTLER_OK ELEV_OK
python3 - "$DIR/evidence_shadow.py" "$SRC_METER" "$TMP/drill.jsonl" "$TMP/butler.jsonl" "$TMP/elev.jsonl" "$TMP" <<'PYEOF'
export CARD_FILTER JSON_ONLY DRILL_OK BUTLER_OK ELEV_OK TICKET_OK
python3 - "$DIR/evidence_shadow.py" "$SRC_METER" "$TMP/drill.jsonl" "$TMP/butler.jsonl" "$TMP/elev.jsonl" "$TMP/tickets.jsonl" "$TMP" <<'PYEOF'
import glob, json, os, sys

sys.path.insert(0, os.path.dirname(os.path.abspath(sys.argv[1])))
import evidence_shadow # noqa: E402 验链与 CI-Workflows 侧同源语义

metering_dir, drill_f, butler_f, elev_f, tmp = sys.argv[2:7]
metering_dir, drill_f, butler_f, elev_f, tickets_f, tmp = sys.argv[2:8]
sources = {"metering": sorted(glob.glob(os.path.join(metering_dir, "shadow-evidence-*.jsonl"))),
"drill": [drill_f] if os.environ.get("DRILL_OK") == "1" else [],
"butler": [butler_f] if os.environ.get("BUTLER_OK") == "1" else [],
"elevation": [elev_f] if os.environ.get("ELEV_OK") == "1" else []}
"elevation": [elev_f] if os.environ.get("ELEV_OK") == "1" else [],
"tickets": [tickets_f] if os.environ.get("TICKET_OK") == "1" else []}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

5. Tickets 绕过完整模式校验 🐞 Bug ≡ Correctness

新增 tickets 文件只经过 verify_file(),该函数不校验 schema v1 必需的 tskindactionverdictactor
或禁止额外字段。于是哈希链正确但 schema 非法的 Go 发射记录仍会被统一查询输出并计入证据统计。
Agent Prompt
## Issue description
统一查询仅复算 tickets 哈希链,没有验证记录满足 evidence schema v1,非法记录可被当作正式证据输出。

## Issue Context
让只读验证路径对每条已带链字段的记录执行完整 schema 校验,并以错误列表而非直接退出方式报告;至少覆盖 required、类型、枚举、时间格式和 additionalProperties。

## Fix Focus Areas
- governance/evidence-query.sh[91-104]
- governance/evidence_shadow.py[57-81]
- governance/evidence_shadow.py[97-119]
- standards/evidence/record.schema.yaml[17-38]
- standards/evidence/record.schema.yaml[59-69]
- standards/evidence/record.schema.yaml[119-128]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

6. 畸形票据返回错误码 🐞 Bug ☼ Reliability

当 tickets 中存在畸形 JSON 时,verify_file() 会记录链错误,但查询随即再次对同一行执行未捕获的 json.loads,在检查 errs 前抛出异常。命令因此返回
Python 的 exit 1 和 traceback,而不是脚本契约规定的链断 exit 3。
Agent Prompt
## Issue description
畸形 tickets JSON 在统一错误处理前被二次解析并抛异常,破坏 exit 3 契约。

## Issue Context
先完成所有源验证;存在任何错误时统一打印并 exit 3,只有验证成功后才解析聚合,或让 verifier 返回已解析记录以避免二次解析。

## Fix Focus Areas
- governance/evidence-query.sh[93-104]
- governance/evidence_shadow.py[97-119]
- governance/tests/test-evidence-shadow.sh[238-251]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

errs, recs = [], []
for src, files in sources.items():
for f in files:
Expand All @@ -106,7 +111,7 @@ for r in out:

summary = {
"total": len(out),
"by_source": {s: sum(1 for r in out if r["source"] == s) for s in ("metering", "drill", "butler", "elevation")},
"by_source": {s: sum(1 for r in out if r["source"] == s) for s in ("metering", "drill", "butler", "elevation", "tickets")},
"by_tenant": {},
"by_card_top": {},
}
Expand Down
3 changes: 2 additions & 1 deletion governance/providers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ entries:
# 三面分离:本二条目=执行面资产申报(判定锚点恒在 GitHub CI,不随池增减改变)
- name: self-cloud-pool
kind: compute # 自有云内网算力池(公网服务器+云电脑池)
entry: 服务器调度器(W2-C1 落地;过渡期=PM 会话经公网服务器驱动云电脑池)
entry: 服务器调度器 v0=cnb-bridge 仓 selfcloud/(W2-C1 已落地:Job Contract
消费+短票据签发+egress allowlist+无状态约束执法;RUNBOOK §5)
placement: server-anchored(池化以服务器为锚,ADR-0103 决策 4)
secrets: [] # 凭据一律走内网域 Vault(本条目零 org secret——分域边界)
catalog: governance/assets-register.yaml(资产归属/额度/license 边界)
Expand Down
61 changes: 49 additions & 12 deletions governance/tests/test-evidence-shadow.sh
Original file line number Diff line number Diff line change
Expand Up @@ -149,19 +149,39 @@ python3 "$DIR/governance/evidence_shadow.py" append --file "$ESHADOW" --event-fi
python3 "$DIR/governance/evidence_shadow.py" verify --file "$ESHADOW" >/dev/null \
&& pass "elevation 影子验链绿" || fail "elevation 影子链断"

# ---- evidence-query 四源统一查询(AC-4a:归并 + 验链 + fail-closed) ----
# ---- tickets 短票据事件(W2-C1 / AC-5b:evidence-query 第 5 源,subject 可查询) ----
TICKETS="$TMP/tickets.jsonl"
cat >"$TMP/tk1.json" <<'EOF'
{"ts": "2026-08-29T00:00:00Z", "kind": "approval", "action": "ticket.grant", "verdict": "pass",
"subject": {"card": "Cloudbird-Software/.github#412", "tenant": "cloudbird-internal"},
"actor": {"identity": "selfcloud-scheduler", "role": "bot"},
"payload": "{\"job_id\": \"job-t1\", \"ttl_minutes\": 30}"}
EOF
cat >"$TMP/tk2.json" <<'EOF'
{"ts": "2026-08-29T00:30:00Z", "kind": "approval", "action": "ticket.revoke", "verdict": "pass",
"subject": {"card": "Cloudbird-Software/.github#412", "tenant": "cloudbird-internal"},
"actor": {"identity": "selfcloud-scheduler", "role": "bot"},
"payload": "{\"job_id\": \"job-t1\", \"reason\": \"ttl-expired\"}"}
EOF
python3 "$DIR/governance/evidence_shadow.py" append --file "$TICKETS" --event-file "$TMP/tk1.json" >/dev/null \
&& python3 "$DIR/governance/evidence_shadow.py" append --file "$TICKETS" --event-file "$TMP/tk2.json" >/dev/null \
&& python3 "$DIR/governance/evidence_shadow.py" verify --file "$TICKETS" >/dev/null \
&& pass "tickets 影子验链绿(grant/revoke 双事件)" || fail "tickets 影子链断"

# ---- evidence-query 五源统一查询(AC-4a:归并 + 验链 + fail-closed) ----
GHSTUB="$TMP/gh-stub"
mkdir -p "$TMP/fixtures"
# fixture:metering=周分片列表($SHADOW);drill/butler/elevation=各自影子(独立成链)
python3 - "$SHADOW" "$DSHADOW" "$BUTLER_SHADOW" "$ESHADOW" "$TMP/fixtures" <<'PYEOF'
# fixture:metering=周分片列表($SHADOW);drill/butler/elevation=各自影子(独立成链);tickets=短票据链
python3 - "$SHADOW" "$DSHADOW" "$BUTLER_SHADOW" "$ESHADOW" "$TICKETS" "$TMP/fixtures" <<'PYEOF'
import base64, json, sys
shadow, dshadow, bshadow, eshadow, fixdir = sys.argv[1:6]
shadow, dshadow, bshadow, eshadow, tickets, fixdir = sys.argv[1:7]
b64 = lambda p: base64.b64encode(open(p, "rb").read()).decode()
json.dump([{"type": "file", "name": "shadow-evidence-2026-W35.jsonl", "content": b64(shadow)}],
open(f"{fixdir}/metering-list.json", "w"))
json.dump({"type": "file", "content": b64(dshadow)}, open(f"{fixdir}/drill.json", "w"))
json.dump({"type": "file", "content": b64(bshadow)}, open(f"{fixdir}/butler.json", "w"))
json.dump({"type": "file", "content": b64(eshadow)}, open(f"{fixdir}/elev.json", "w"))
json.dump({"type": "file", "content": b64(tickets)}, open(f"{fixdir}/tickets.json", "w"))
PYEOF
cat >"$GHSTUB" <<'STUBEOF'
#!/usr/bin/env bash
Expand All @@ -185,9 +205,14 @@ case "$url" in
cat "$F/butler.json" ;;
"repos/Cloudbird-Software/.github/contents/governance/elevation/shadow-evidence.jsonl?ref=elevation-ledger")
if [[ "${GH_STUB_ELEV_MISSING:-}" == "1" ]]; then
echo 'gh: No commit found for the ref elevation-ledger (HTTP 404)' >&2; exit 1
echo "gh: No commit found for the ref elevation-ledger (HTTP 404)" >&2; exit 1
fi
cat "$F/elev.json" ;;
"repos/Cloudbird-Software/cnb-bridge/contents/tickets.jsonl?ref=tickets-ledger")
if [[ "${GH_STUB_TICKETS_MISSING:-}" == "1" ]]; then
echo "gh: No commit found for the ref tickets-ledger (HTTP 404)" >&2; exit 1
fi
cat "$F/tickets.json" ;;
*) echo "gh-stub: 意外 URL $url" >&2; exit 1 ;;
esac
STUBEOF
Expand All @@ -196,10 +221,10 @@ nlines() { grep -c . <<<"$1" || true; } # 非空行计数(wc 对空 herestrin
QOUT=$(GH="$GHSTUB" GH_STUB_FIXTURES="$TMP/fixtures" GH_TOKEN=stub \
bash "$DIR/governance/evidence-query.sh" 2>"$TMP/q.err"); QRC=$?
N=$(nlines "$QOUT")
[[ $QRC -eq 0 && "$N" -eq 6 ]] && pass "统一查询归并四源(6 条,rc=0)" || fail "统一查询(rc=$QRC 行=$N)"
[[ $QRC -eq 0 && "$N" -eq 8 ]] && pass "统一查询归并五源(8 条,rc=0)" || fail "统一查询(rc=$QRC 行=$N)"
grep -q '"source":"metering"' <<<"$QOUT" && grep -q '"source":"drill"' <<<"$QOUT" && grep -q '"source":"butler"' <<<"$QOUT" \
&& grep -q '"source":"elevation"' <<<"$QOUT" \
&& pass "四源标记齐全(source 字段)" || fail "source 标记缺失"
&& grep -q '"source":"elevation"' <<<"$QOUT" && grep -q '"source":"tickets"' <<<"$QOUT" \
&& pass "五源标记齐全(source 字段)" || fail "source 标记缺失"
grep -q '^SUMMARY ' "$TMP/q.err" && grep -Eq '"metering": ?2' "$TMP/q.err" \
&& pass "分源统计(stderr SUMMARY)" || fail "SUMMARY 统计缺失"
# --card 过滤:metering 影子绑 #407,drill/butler 哨兵 #0,elevation 绑 #415
Expand All @@ -213,28 +238,40 @@ EOUT=$(GH="$GHSTUB" GH_STUB_FIXTURES="$TMP/fixtures" GH_TOKEN=stub \
EN=$(nlines "$EOUT")
[[ $ERC -eq 0 && "$EN" -eq 1 ]] && grep -q '"source":"elevation"' <<<"$EOUT" \
&& pass "AC-9c elevation 记录 subject 可查询(#415 → 1 条)" || fail "elevation 查询(rc=$ERC 行=$EN)"
# AC-5b:短票据 grant/revoke 事件按 subject 可查询(#412 → 2 条,source=tickets)
TQOUT=$(GH="$GHSTUB" GH_STUB_FIXTURES="$TMP/fixtures" GH_TOKEN=stub \
bash "$DIR/governance/evidence-query.sh" --card Cloudbird-Software/.github#412 2>/dev/null); TQRC=$?
TQN=$(nlines "$TQOUT")
[[ $TQRC -eq 0 && "$TQN" -eq 2 ]] && grep -q '"source":"tickets"' <<<"$TQOUT" \
&& grep -q '"ticket.grant"' <<<"$TQOUT" && grep -q '"ticket.revoke"' <<<"$TQOUT" \
&& pass "AC-5b tickets 短票据事件 subject 可查询(#412 → 2 条)" || fail "tickets 查询(rc=$TQRC 行=$TQN)"
# 源缺席(404):过渡期合法,非红(须在篡改 fixture 生成前跑——桩对篡改片优先回放)
MOUT=$(GH="$GHSTUB" GH_STUB_FIXTURES="$TMP/fixtures" GH_TOKEN=stub GH_STUB_BUTLER_MISSING=1 \
bash "$DIR/governance/evidence-query.sh" 2>"$TMP/m.err"); MRC=$?
MN=$(nlines "$MOUT")
[[ $MRC -eq 0 && "$MN" -eq 5 ]] && pass "butler 源缺席(404)→ 跳过非红(5 条)" || fail "源缺席误红(rc=$MRC 行=$MN)"
[[ $MRC -eq 0 && "$MN" -eq 7 ]] && pass "butler 源缺席(404)→ 跳过非红(7 条)" || fail "源缺席误红(rc=$MRC 行=$MN)"
# 源缺席(404 第二报文形态——账本分支未建 "No ref found"):同跳过非红
# (2026-08-29 波次通道实测回归:该形态曾被误判"非 404"→ INFRA exit 2)
ROUT=$(GH="$GHSTUB" GH_STUB_FIXTURES="$TMP/fixtures" GH_TOKEN=stub GH_STUB_BUTLER_MISSING=2 \
bash "$DIR/governance/evidence-query.sh" 2>"$TMP/r.err"); RRC=$?
RN=$(nlines "$ROUT")
[[ $RRC -eq 0 && "$RN" -eq 5 ]] && pass "源缺席(No ref found 分支未建)→ 跳过非红(5 条)" || fail "分支未建误红(rc=$RRC 行=$RN)"
[[ $RRC -eq 0 && "$RN" -eq 7 ]] && pass "源缺席(No ref found 分支未建)→ 跳过非红(7 条)" || fail "分支未建误红(rc=$RRC 行=$RN)"
# 源缺席(404 第三报文形态——分支缺失 "No commit found for the ref",本地实测
# 真实报文):按 HTTP 404 状态码判定后同跳过非红
COUT2=$(GH="$GHSTUB" GH_STUB_FIXTURES="$TMP/fixtures" GH_TOKEN=stub GH_STUB_BUTLER_MISSING=3 \
bash "$DIR/governance/evidence-query.sh" 2>"$TMP/c2.err"); C2RC=$?
C2N=$(nlines "$COUT2")
[[ $C2RC -eq 0 && "$C2N" -eq 5 ]] && pass "源缺席(No commit found for the ref)→ 跳过非红(5 条)" || fail "分支缺失误红(rc=$C2RC 行=$C2N)"
[[ $C2RC -eq 0 && "$C2N" -eq 7 ]] && pass "源缺席(No commit found for the ref)→ 跳过非红(7 条)" || fail "分支缺失误红(rc=$C2RC 行=$C2N)"
# 源缺席(elevation 第 4 源分支未建——"No commit found"):同跳过非红
EOUT2=$(GH="$GHSTUB" GH_STUB_FIXTURES="$TMP/fixtures" GH_TOKEN=stub GH_STUB_ELEV_MISSING=1 \
bash "$DIR/governance/evidence-query.sh" 2>"$TMP/e2.err"); E2RC=$?
E2N=$(nlines "$EOUT2")
[[ $E2RC -eq 0 && "$E2N" -eq 5 ]] && pass "elevation 源缺席(分支未建)→ 跳过非红(5 条)" || fail "elevation 缺席误红(rc=$E2RC 行=$E2N)"
[[ $E2RC -eq 0 && "$E2N" -eq 7 ]] && pass "elevation 源缺席(分支未建)→ 跳过非红(7 条)" || fail "elevation 缺席误红(rc=$E2RC 行=$E2N)"
# tickets 第 5 源缺席(分支未建):同跳过非红(REMOVAL 语义:源消失≠链断)
TKOUT=$(GH="$GHSTUB" GH_STUB_FIXTURES="$TMP/fixtures" GH_TOKEN=stub GH_STUB_TICKETS_MISSING=1 \
bash "$DIR/governance/evidence-query.sh" 2>"$TMP/tk.err"); TKRC=$?
TKQN=$(nlines "$TKOUT")
[[ $TKRC -eq 0 && "$TKQN" -eq 6 ]] && pass "tickets 源缺席(分支未建)→ 跳过非红(6 条)" || fail "tickets 缺席误红(rc=$TKRC 行=$TKQN)"
# 负向:任一源链断 → exit 3 且 stdout 零输出(不可信数据不出结果)
python3 - "$TMP/fixtures" <<'PYEOF'
import base64, json, sys
Expand Down