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
65 changes: 58 additions & 7 deletions .github/workflows/gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,22 +154,73 @@ jobs:
echo "::error::C1 路径变更(governance/standards/scripts/.github/CODEOWNERS)但 PR 未引用任何 ADR-NNNN(GOVERNANCE flows.governance_change C1:无 ADR 不合并)"
exit 1
fi
# 存在性校验(ADR-0021):被引 ADR 须存在于公开仓 agent-registry/decisions/
#(GITHUB_TOKEN 读公开仓;API 失败 fail-closed——检测器失明不得伪装通过)
ADR_LISTING=$(gh api "repos/Cloudbird-Software/agent-registry/contents/decisions?per_page=100" --paginate --jq '.[].name' 2>/dev/null)
if [[ -z "$ADR_LISTING" ]]; then
echo "::error::agent-registry/decisions 清单拉取失败——ADR 引用存在性无法校验(fail-closed)"
# 存在性校验(ADR-0021)+ 索引世界(ADR-0053/W1-C1 .github#164)双世界兼容:
# 先试拉 agent-registry decisions/INDEX.yaml(墓碑索引)——
# 200 → 索引世界(迁移后):decisions/ 只留同名墓碑(文件名保留正是为了
# 让本步骤的存在性清单与 org-gate v1.4.2 按文件名校验零改动兼容),
# 正本在 archive 仓 adr/。被引 ADR-NNNN 必须在 INDEX entries 且
# archive_path 非空,并 HEAD 校验 archive 正本可达(raw 200)。
# 404 → 旧世界(迁移未发生/已回滚):现有 decisions/ 清单逻辑原样。
# 其他 → fail-closed(检测器失明不得伪装通过)。
# 两世界共通不变量:引用不存在的编号一律 fail(防幽灵 ADR 语义不回归,#164 AC-2)。
ADR_INDEX_JSON=$(gh api "repos/Cloudbird-Software/agent-registry/contents/decisions/INDEX.yaml" 2>&1 >/tmp/adr_index.json; echo "rc=$?")
if jq -e '.content' /tmp/adr_index.json >/dev/null 2>&1; then
ADR_WORLD="index"
base64 -d <<<"$(jq -r .content /tmp/adr_index.json)" >/tmp/adr_index.yaml 2>/dev/null
# entry → "NNNN archive_path" 行(pyyaml 已随本 job 前置步骤安装);
# archive_path 为空的 entry 不入 map(#96 扩展点:后续 substantive 字段
# 不影响本映射——number/archive_path 是本关卡唯一消费的键)
python3 -c '
import yaml
d = yaml.safe_load(open("/tmp/adr_index.yaml", encoding="utf-8"))
for e in d.get("entries") or []:
p = e.get("archive_path")
if p:
print(f"{int(e[\"number\"]):04d} {p}")' >/tmp/adr_map.txt
if [[ ! -s /tmp/adr_map.txt ]]; then
echo "::error::INDEX.yaml 存在但 entries 为空/解析失败——索引世界不可判定,fail-closed"
exit 1
fi
elif grep -q "Not Found" <<<"$ADR_INDEX_JSON"; then
ADR_WORLD="legacy"
else
echo "::error::agent-registry decisions/INDEX.yaml 拉取失败(非 404)——索引世界判定失败,fail-closed"
exit 1
fi
if [[ "$ADR_WORLD" == "legacy" ]]; then
ADR_LISTING=$(gh api "repos/Cloudbird-Software/agent-registry/contents/decisions?per_page=100" --paginate --jq '.[].name' 2>/dev/null)
if [[ -z "$ADR_LISTING" ]]; then
echo "::error::agent-registry/decisions 清单拉取失败——ADR 引用存在性无法校验(fail-closed)"
exit 1
fi
fi
MISSING=0
for ref in $( { echo "$PR_TITLE"; echo "$PR_BODY"; } | grep -oE "$ADR_RE" | sort -u ); do
num="${ref#ADR-}"
if ! grep -q "^ADR-${num}-" <<<"$ADR_LISTING"; then
if [[ "$ADR_WORLD" == "index" ]]; then
# 索引世界:编号→archive_path→正本可达性(raw HEAD 200;网络/5xx 视同失败)
paths=$(awk -v n="$num" '$1 == n {print $2}' /tmp/adr_map.txt)
if [[ -z "$paths" ]]; then
echo "::error::引用的 ${ref} 不在 INDEX entries(幽灵 ADR——C1 决策背书不成立)"
MISSING=1
continue
fi
reachable=0
while IFS= read -r apath; do
code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 20 \
"https://raw.githubusercontent.com/Cloudbird-Software/archive/main/${apath}" || echo 000)
Comment on lines +210 to +211
[[ "$code" == "200" ]] && reachable=1 && break
done <<<"$paths"
Comment on lines +210 to +213

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

3. Flaky raw fetch checks 🐞 Bug ☼ Reliability

adr-required and drift-check index-world validation use curl to raw.githubusercontent.com with a
hard 20s timeout but no retry/backoff, so transient network/DNS/CDN issues can block merges (gate)
or create spurious drift (drift-check).
Agent Prompt
### Issue description
The PR adds raw.githubusercontent.com reachability/content fetches using `curl` with `--max-time 20` but no retries. This makes required checks (gate) and scheduled enforcement (drift-check) sensitive to transient network errors.

### Issue Context
This is new behavior introduced with the ADR index-world support (archive raw fetch). Retrying does not weaken fail-closed semantics; it reduces false negatives caused by ephemeral transport failures.

### Fix Focus Areas
- .github/workflows/gate.yml[199-216]
- governance/drift-check.sh[306-325]

### Implementation notes
- Add retry flags such as `--retry 3 --retry-all-errors --retry-delay 1` (optionally `--connect-timeout 5`).
- Keep the existing failure behavior after retries (still fail-closed if unreachable).

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

if [[ $reachable -ne 1 ]]; then
echo "::error::${ref} 的 archive 正本不可达(${paths//$'\n'/,}——raw 非 200):INDEX 与 archive 仓漂移或归档未落位"
MISSING=1
fi
elif ! grep -q "^ADR-${num}-" <<<"$ADR_LISTING"; then
echo "::error::引用的 ${ref} 在 agent-registry/decisions/ 无对应文件(幽灵 ADR——C1 决策背书不成立)"
MISSING=1
fi
done
if [[ $MISSING -eq 0 ]]; then
echo "OK adr-required: $( { echo "$PR_TITLE"; echo "$PR_BODY"; } | grep -oE "$ADR_RE" | sort -u | tr '\n' ' ')(存在性已验;实体性后验:drift-check §10)"
echo "OK adr-required[${ADR_WORLD}]: $( { echo "$PR_TITLE"; echo "$PR_BODY"; } | grep -oE "$ADR_RE" | sort -u | tr '\n' ' ')(存在性+正本可达性已验;实体性后验:drift-check §10)"
fi
exit $MISSING
9 changes: 9 additions & 0 deletions governance/REPOS.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ repos:
# 门禁,ADR-0015)此前不在组织地图——门禁自身逃逸治理地图
policies: [agent_runtime]

- name: archive
layer: L1
visibility: public
status: active
role: 记忆层——ADR 归档正本(append-only,字节保真)+规划回归集+事件 JSONL+红队报告
落位(宪法 §1/§13 推论二,W1-C1 .github#164/ADR-0053);ADR 状态真源=agent-registry
decisions/INDEX.yaml 墓碑索引(ADR-0053)
key_paths: [adr/, scripts/verify_migration.py]

- name: template-service
layer: L2
visibility: public
Expand Down
63 changes: 52 additions & 11 deletions governance/drift-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -284,11 +284,15 @@ for r in $REPOS; do
done

# ---------- 10. ADR 引用存在性+实体性后验(adr-required 的补充防线,评审项 + RB-D5)----------
# gate.yml 的 adr-required 在 PR 上下文只能做语法检查:agent-registry 是私有仓,
# PR 上下文的 GITHUB_TOKEN 无跨仓读权,注入 org secret 又会向 PR 控制的代码暴露
# 凭据。存在性在本节后验:窗口内合并 PR 的 ADR-NNNN 引用必须真实存在于
# agent-registry/decisions/——伪造/幽灵 ADR 最长 24h 内被检出(与 §8 直推检测
# 同为 post-hoc 防线;C1 的权威人类门禁仍是 owner-only review)。
# gate.yml 的 adr-required 已在 PR 时点做存在性校验(ADR-0021:全仓公开后 PR 上下文
# GITHUB_TOKEN 可读公开仓——早期"agent-registry 是私有仓、PR 时点无跨仓读权"的前提
# 已随 ADR-0020 全仓公开政策失效,注记修正);ADR-0053(W1-C1 .github#164)索引世界
# 起进一步校验 archive 正本可达性。本节后验实体性(防空壳)并独立复核存在性:
# 窗口内合并 PR 的 ADR-NNNN 引用必须有实质决策结构——伪造/幽灵 ADR 最长 24h 内
# 检出(与 §8 直推检测同为 post-hoc 防线;C1 的权威人类门禁仍是 owner-only review)。
# 内容源双世界(ADR-0053):agent-registry/decisions/INDEX.yaml(墓碑索引)存在 →
# decisions/ 只剩同名墓碑(无实质结构),实体性改验 INDEX 指向的 archive 仓正本
# (字节保真原件);INDEX 404(迁移前/回滚)→ 旧逻辑对 decisions/ 本体验证。
ADR_RE='ADR-[0-9]{4}'
# 实体性判定(评审项:size 字节数可被空白/注释/填充绕过——100B 阈值挡不住
# RB-D5 意义上的空壳):拉取被引 ADR 全文做结构校验,H1 编号行、status 行、
Expand All @@ -299,16 +303,26 @@ declare -A ADR_VERDICT_CACHE
adr_substantive() { # $1=四位编号 → stdout: missing|ok|shell|unreadable
local num="$1" matches apath content decoded verdict
[[ -n "${ADR_VERDICT_CACHE[$num]:-}" ]] && { echo "${ADR_VERDICT_CACHE[$num]}"; return; }
matches=$(jq -r --arg p "ADR-${num}-" '.[] | select((.name | startswith($p)) and (.type == "file")) | .path' <<<"$ADR_DIR_LISTING")
if [[ -n "${ADR_INDEX_MODE:-}" ]]; then
# 索引世界(ADR-0053):编号→archive 正本路径(可多条:ADR-0011 双档先例)
matches=$(awk -v n="$num" '$1 == n {print $2}' <<<"$ADR_INDEX_MAP")
else
matches=$(jq -r --arg p "ADR-${num}-" '.[] | select((.name | startswith($p)) and (.type == "file")) | .path' <<<"$ADR_DIR_LISTING")
fi
if [[ -z "$matches" ]]; then
verdict="missing"
else
verdict="unreadable"
while IFS= read -r apath; do
[[ -n "$apath" ]] || continue
content=$(api "https://api.github.com/repos/$ORG/agent-registry/contents/$apath" | jq -r '.content // empty')
[[ -z "$content" ]] && continue
decoded=$(base64 -d <<<"$content" 2>/dev/null || true)
if [[ -n "${ADR_INDEX_MODE:-}" ]]; then
# 索引世界:archive raw 正本(公开仓,字节保真原件);拉取失败留空→按不可判定处理
decoded=$(curl -sSf --max-time 20 "https://raw.githubusercontent.com/$ORG/archive/main/${apath}" 2>/dev/null || true)
else
content=$(api "https://api.github.com/repos/$ORG/agent-registry/contents/$apath" | jq -r '.content // empty')
[[ -z "$content" ]] && continue
decoded=$(base64 -d <<<"$content" 2>/dev/null || true)
fi
[[ -z "$decoded" ]] && continue
if grep -qE "^#[[:space:]]*ADR-${num}([^0-9]|$)" <<<"$decoded" \
&& grep -qE "^-[[:space:]]*(status|状态):[[:space:]]*[^[:space:]]" <<<"$decoded" \
Expand All @@ -330,6 +344,28 @@ ADR_DIR_LISTING=$(api "https://api.github.com/repos/$ORG/agent-registry/contents
if ! jq -e 'type == "array"' <<<"$ADR_DIR_LISTING" >/dev/null 2>&1; then
drift "ADR 真源 agent-registry/decisions 读取失败,引用存在性无法后验(fail-closed): $(jq -r '.message // "非数组"' <<<"$ADR_DIR_LISTING" 2>/dev/null || echo 传输失败)"
else
# 墓碑索引探测(ADR-0053):200 → 索引世界(map:编号→archive 正本路径);
# 404 → 旧世界;其他失败 → 报漂移(fail-closed:检测器失明不得伪装无漂移),
# 并降级旧世界继续跑完本节(漂移行已置红,后续节不受影响)。
ADR_INDEX_MODE=""
ADR_INDEX_MAP=""
ADR_INDEX_JSON=$(api "https://api.github.com/repos/$ORG/agent-registry/contents/decisions/INDEX.yaml")
if jq -e '.content' <<<"$ADR_INDEX_JSON" >/dev/null 2>&1; then
ADR_INDEX_MAP=$(base64 -d <<<"$(jq -r '.content' <<<"$ADR_INDEX_JSON")" 2>/dev/null | awk '
/^[[:space:]]*-[[:space:]]*number:/ { n=$3; gsub(/[^0-9]/, "", n); cur=sprintf("%04d", n+0); ap="" }
/^[[:space:]]*archive_path:/ {
line=$0; sub(/^[[:space:]]*archive_path:[[:space:]]*/, "", line); gsub(/"/, "", line)
Comment on lines +354 to +357

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

2. Brittle index.yaml parsing 🐞 Bug ≡ Correctness

drift-check §10 parses decisions/INDEX.yaml via regex/field-splitting awk on decoded YAML, which is
not YAML-aware and can mis-parse valid YAML (e.g., inline comments/quoting/format changes),
producing an empty/malformed ADR_INDEX_MAP and causing false drift or disabling index mode.
Agent Prompt
### Issue description
`governance/drift-check.sh` currently decodes `decisions/INDEX.yaml` and then uses `awk` regexes to extract `number` and `archive_path`. This is brittle because YAML is not line/field stable; small formatting changes can break the map and trigger false drift or prevent index-world ADR validation.

### Issue Context
The script already relies on Python in other sections (e.g., §7 REPOS.yaml parsing), so a robust YAML parse is available and consistent with gate.yml’s approach.

### Fix Focus Areas
- governance/drift-check.sh[146-160]
- governance/drift-check.sh[347-365]

### Implementation notes
- Replace the `base64 -d | awk ...` extraction with a Python snippet using `yaml.safe_load`.
- Emit lines `NNNN <archive_path>` (zero-padded) for each entry with non-empty `archive_path`, allowing duplicates (ADR-0011 dual-track).
- Keep existing fail-closed behavior: if parsing yields no valid mappings, emit the current drift message and do not enable `ADR_INDEX_MODE`.

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

if (cur != "" && line != "") { print cur " " line; cur="" }
}')
if [[ -n "$ADR_INDEX_MAP" ]] && grep -qE '^[0-9]{4} adr/.+\.md$' <<<"$ADR_INDEX_MAP"; then
ADR_INDEX_MODE=1
ok "adr-index(ADR-0053 索引世界:$(wc -l <<<"$ADR_INDEX_MAP") entries,实体性改验 archive 正本)"
else
drift "decisions/INDEX.yaml 存在但解析为空/畸形——索引世界不可判定(fail-closed,按旧世界降级继续)"
Comment on lines +360 to +364
fi
elif ! grep -q '"message"[[:space:]]*:[[:space:]]*"Not Found"' <<<"$ADR_INDEX_JSON"; then
drift "decisions/INDEX.yaml 探测失败(非 404 的 API 错误)——索引世界判定失败(fail-closed,按旧世界降级继续)"
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.
ADR_FILES=$(jq -r '.[].name' <<<"$ADR_DIR_LISTING")
GHOST=0
for r in $REPOS; do
Expand All @@ -347,10 +383,15 @@ else
case "$(adr_substantive "$num")" in
ok) : ;;
missing)
drift "repo '$r' PR#$pnum 引用幽灵 ADR ${ref}(agent-registry/decisions/ 无 ADR-${num}-*.md——C1 变更的决策背书不成立)"
if [[ -n "$ADR_INDEX_MODE" ]]; then
where="INDEX entries 无此编号"
else
where="agent-registry/decisions/ 无 ADR-${num}-*.md"
fi
drift "repo '$r' PR#$pnum 引用幽灵 ADR ${ref}(${where}——C1 变更的决策背书不成立)"
GHOST=1 ;;
shell)
drift "repo '$r' PR#$pnum 引用空壳 ADR ${ref}(文件存在但缺实质结构:H1 编号行/status/背景/决策章节须齐备且决策节有正文——红队 RB-D5:字节数填充不再能绕过)"
drift "repo '$r' PR#$pnum 引用空壳 ADR ${ref}(正本存在但缺实质结构:H1 编号行/status/背景/决策章节须齐备且决策节有正文——红队 RB-D5:字节数填充不再能绕过)"
GHOST=1 ;;
unreadable)
drift "repo '$r' PR#$pnum 引用的 ADR ${ref} 全部同名文件内容读取失败,实体性无法判定(fail-closed)"
Expand Down
7 changes: 5 additions & 2 deletions governance/expected-state.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@
]
},
"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)。",
"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"
Expand Down Expand Up @@ -153,6 +153,9 @@
],
"arbiter": [
"7b1a65234b1697bde11cea2ebdacd8ee0efc44c4"
],
"archive": [
"a32f0fcea2bc7b422ad8ad1463a8ad4a92cbeb60"
]
},
"merge_queue": {
Expand Down Expand Up @@ -180,4 +183,4 @@
"ref": "refs/tags/v1.4.2",
"ref_commit": "2d368c29f170eab43b83f6b866f76fad3774fba9"
}
}
}