Add evidence gates: facts registry, evidence tooling, and precheck checks

Why: declarations were passing as capability; hand-transcribed outputs flowed
through cards and PR bodies as if they were execution evidence, and stale
external facts (e.g. the dead git-panel host) kept being trusted until burned.

Evidence: guidelines draft.10 (PR Development-Guidelines#4, merged af199ee)
pinned here; policy/facts.md F1-F4 each re-verified on 2026-10-05 before
registration (endpoint version, token HTTP 200 via header only, SSH ls-remote,
dead-host negative probe); scripts syntax-checked (bash -n); tool self-tests
and make test recorded in the task card via the new make evidence.

Impact: new AGENTS.md section (SELF/REPLAY/GATE, tool-written evidence,
replay-on-review, zero-trust facts, honest-empty); make evidence/facts-verify;
precheck gains [hooks]/[facts], task-sync gains stale-evidence-HEAD warning;
task-start cards now embed the evidence table; gitlink advances to draft.10.

Refs: task card .task/feature-evidence-driven-gates.md; guidelines CHANGELOG
0.1.0-draft.10.
This commit is contained in:
2026-10-05 10:47:27 +00:00
parent fb99968922
commit ba83a17c11
8 changed files with 211 additions and 8 deletions
+14 -2
View File
@@ -6,11 +6,23 @@
- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 602d2ce9c6900343155f2a9c7ccb4f1c83aa4a59
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-evidence-driven-gates(worktree);分支:feature/evidence-driven-gates
- 授权与交付终点:<本次允许的动作与终点;未授权事项写明>
- 授权与交付终点:用户 2026-10-05 授权「按证据驱动方案完善规范」——guidelines 子模块(PR)+
Dev 仓(AGENTS.md/policy/工具/Makefile/task.sh + gitlink 推进)经 `make test` 组合验证后
PR 合入 main 并回收工作区。未授权:改前端/后端业务代码、解除分支保护、改写历史。
## 目标与非目标
<预期结果、受影响入口/模块/契约;明确不做什么。>
目标:落地「声明≠能力」证据门禁——
1) guidelines draft.10:证据来源三分 SELF/REPLAY/GATE、工具写入、审阅者重放、外部事实
零信任、诚实出口(verification/collaboration/delivery/baseline/glossary/两模板/CHANGELOG)。
2) Dev 仓:AGENTS.md「证据与验证门禁」节;policy/facts.md 事实登记表(F1–F4 实测后登记);
scripts/evidence.sh(make evidence:命令×HEAD×sha256×尾部写入任务卡);
scripts/facts-verify.sh(make facts-verify [--update]);task.sh:precheck 增 [hooks]/[facts],
sync 增 [evidence] 旧 HEAD 告警,task-start 卡片模板带证据表;Makefile 增两目标。
3) gitlink 推进至 guidelines 合入 SHA(af199ee),组合验证 make test。
非目标:task-close 核对 CI 状态(PR3 权威迁移)、CI 工作流改动、UI/业务代码、
review 技能文本改造(后续任务)。
## 同步记录
+29 -2
View File
@@ -36,6 +36,29 @@
`feature/card-close-*` 分支+PR,任务分支内加 `FOLD=--fold` 随当前 PR 合入。
precheck 的 `[cards]` 行报告欠账,验收前应清零;闭合 PR 由所有者合入。
## 证据与验证门禁(强制)
原则:**「声明已就位」≠「能力已就位」**。任何来源(用户输入、方案、报告、他人或自己的
叙述)的结果声明都不是证据;证据 = 命令 × 被测对象 SHA × 可复核输出,三者齐备才算事实。
规范依据:guidelines `standards/verification.md`「声明与证据来源」(draft.10)。
- **来源三分**:产出物(任务卡、PR、报告、review 结论)中的通过类断言须标注来源——
`GATE`(CI/分支保护/precheck 实测)/ `REPLAY`(审阅者独立重跑)/ `SELF`(作者粘贴)。
合入与验收判定只认 GATE/REPLAY;SELF 仅为线索,与重验不一致时以重验为准。
- **证据由工具写入**:命令输出进入任务卡/PR 一律经 `make evidence CMD="…"` 写入
(自动记录命令、被测 HEAD、退出码、输出 sha256 与尾部摘录并脱敏 token)。手抄、转写、
凭记忆重构的输出视为无效证据;补写输出与伪造测试同级红线。
- **完成声明三段输出**(precheck / task-sync / 测试)必须由 evidence 工具写入任务卡,
PR 描述引用任务卡段落,不另手抄。测试通过的权威源是 CI(`.gitea/workflows/ci.yaml`,
在 PR HEAD 上 `make test`+`make build`),本地输出降级为辅助线索。
- **验收即重放**:请求审核后,审阅者先在评审环境重放最小重放集(关键门禁命令 + 变更
直接相关检查)再读作者证据;重放与声明不一致直接阻塞,并作为失实事件登记。
- **外部事实零信任**:用户/文档/旧报告里的「可用/已就位」必须先执行 `policy/facts.md`
登记表中对应验证命令(或当场实测)才能写进产出物;无法当场复核的结论标 `[未经证实]`,
禁止裸写。登记表每条事实带 `last_verified` 与 TTL,precheck `[facts]` 报告过期项,
`make facts-verify` 逐条重验。
- **诚实出口**:未执行/未验证是合法状态,如实标注(NOT_RUN/未验证)优于补写。
## 提交与 PR
- 主题行:英文祈使句一行(≤100 列,不以句点结尾);body 写 `Why: / Evidence: / Impact: / Refs:`
@@ -44,10 +67,14 @@
- **高后果操作双钥匙**:改写历史、解除/变更分支保护、删除归档引用、发布版本——Agent 只准备
方案与命令,由用户确认并亲手执行;Agent 不得自行实施(baseline 硬边界 1 的授权范围要求)。
- 模块边界、公开契约与隔离档位见 `policy/modules/`(如有)与 PROJECT.md。
- 验证命令见 PROJECT.md「验证」节;完成声明须附实际命令输出证据。
- 验证命令见 PROJECT.md「验证」节;完成声明的三段输出按「证据与验证门禁」节由
`make evidence` 写入任务卡,不手抄。
- 首次克隆本仓后运行 `make setup` 启用本地 pre-commit 钩子。
## Gitea 与 Git 访问事实(2026-09-30 实测)
## Gitea 与 Git 访问事实(2026-09-30 实测;登记表 `policy/facts.md`)
机器可核的登记表在 `policy/facts.md`(每条带验证命令、last_verified 与 TTL);
引用任一事实前先跑其验证命令。以下为叙述版:
历史任务卡与旧脚本里的 `git-panel.u1680832.nyat.app:30319` 已失效(TLS 握手失败),
见到一律按过期处理,不要再试。当前权威通道:
+12 -2
View File
@@ -13,7 +13,7 @@ PLUGINS := plugins
COMBO := core=$(shell git -C $(CORE) rev-parse --short HEAD 2>/dev/null)+backend=$(shell git -C $(BACKEND) rev-parse --short HEAD 2>/dev/null)+frontend=$(shell git -C $(FRONTEND) rev-parse --short HEAD 2>/dev/null)+ui=$(shell git -C $(UI) rev-parse --short HEAD 2>/dev/null)+agentcmd=$(shell git -C deps/agent-command rev-parse --short HEAD 2>/dev/null)+documents=$(shell git -C modules/documents rev-parse --short HEAD 2>/dev/null)+native=$(shell git -C deps/agent-native rev-parse --short HEAD 2>/dev/null)+squad=$(shell git -C deps/agent-squad rev-parse --short HEAD 2>/dev/null)+intent=$(shell git -C deps/agent-intent rev-parse --short HEAD 2>/dev/null)
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null)+$(COMBO)
.PHONY: help sync status build frontend svc svc-list svc-info svc-commits svc-history svc-update test-backend test-frontend test-ui test-core test-agentcmd test-pins test-libs dev pin clean setup precheck task-start task-sync task-close task-finish version
.PHONY: help sync status build frontend svc svc-list svc-info svc-commits svc-history svc-update test-backend test-frontend test-ui test-core test-agentcmd test-pins test-libs dev pin clean setup precheck task-start task-sync task-close task-finish facts-verify evidence version
version:
@echo "agent-system $(VERSION)"
@@ -22,7 +22,7 @@ TASK := scripts/task.sh
setup:
git config core.hooksPath scripts/hooks
chmod +x scripts/hooks/pre-commit scripts/hooks/post-merge scripts/task.sh
chmod +x scripts/hooks/pre-commit scripts/hooks/post-merge scripts/task.sh scripts/evidence.sh scripts/facts-verify.sh
@echo "OK: pre-commit/post-merge 钩子已启用(core.hooksPath=scripts/hooks)"
precheck:
@@ -41,6 +41,16 @@ task-close:
task-finish:
@bash $(TASK) finish $(ABANDON)
# 证据写入:执行命令并把结果行追加进当前任务卡(AGENTS.md「证据与验证门禁」)。
# 用法:make evidence CMD="<单行命令>" [SRC=SELF|REPLAY|GATE] [TAIL=15];退出码随被执行命令。
evidence:
@test -n "$(CMD)" || (echo 'usage: make evidence CMD="<command>" [SRC=SELF|REPLAY|GATE] [TAIL=15]'; exit 2)
@CMD='$(CMD)' SRC='$(SRC)' TAIL='$(TAIL)' bash scripts/evidence.sh
# 外部事实登记表逐条重验(--update 刷新 PASS 项 last_verified)。
facts-verify:
@bash scripts/facts-verify.sh $(ARGS)
help:
@echo "make sync 物化全部 submodule(递归)"
@echo "make build 构建单二进制 bin/agent-system(契约同步→前端构建→embed→静态链接)"
+16
View File
@@ -0,0 +1,16 @@
# 项目外部事实登记表
规则见 AGENTS.md「证据与验证门禁」与 deps/development-guidelines/standards/verification.md「声明与证据来源」。
- 每条事实 = 陈述 + 验证命令 + last_verified + TTL;**引用前必须先执行其验证命令**(外部事实零信任)。
- 过期(>TTL)必须重验后更新:`make facts-verify` 逐条重验,`make facts-verify ARGS=--update`
把 PASS 项的 last_verified 刷新为当天并写回本表(随 PR 合入)。
- 表内验证命令不得含竖线 `|`(保持表格与解析可核);新增事实先实测再登记,不得凭记忆填日期。
- precheck 的 `[facts]` 行报告过期项;AGENTS.md「Gitea 与 Git 访问事实」节为本表的叙述版。
| ID | 事实 | 验证命令 | last_verified | TTL(天) |
|---|---|---|---|---|
| F1 | Gitea Web/API 可达:https://git-home.u1680832.nyat.app:30319(1.27.3) | `curl -fsS -m 10 https://git-home.u1680832.nyat.app:30319/api/v1/version` | 2026-10-05 | 30 |
| F2 | API token 存在且可用(值在 ~/.secrets/gitea,不入仓不回显) | `t=$(sed -n "s/^ACCESS_TOKEN=//p" ~/.secrets/gitea); test -n "$t" && curl -fsS -m 10 -o /dev/null -H "Authorization: token $t" https://git-home.u1680832.nyat.app:30319/api/v1/user` | 2026-10-05 | 30 |
| F3 | Git over SSH 可达:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/&lt;Repo&gt;.git(key ~/.ssh/home_gitea) | `GIT_SSH_COMMAND="ssh -i ~/.ssh/home_gitea -o BatchMode=yes" git ls-remote ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Development-Guidelines.git HEAD` | 2026-10-05 | 30 |
| F4 | 旧端点 git-panel.u1680832.nyat.app:30319 已失效(TLS 握手失败),不得使用 | `! curl -fsS -m 8 https://git-panel.u1680832.nyat.app:30319/api/v1/version` | 2026-10-05 | 180 |
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
# 证据写入工具:执行命令,把(命令、被测 HEAD、退出码、输出 sha256、尾部摘录、时间、来源)
# 追加到当前分支任务卡的「## 证据(Evidence)」表。规范依据:AGENTS.md「证据与验证门禁」、
# guidelines standards/verification.md「声明与证据来源」——转写/手抄的输出视为无效证据。
# 用法:make evidence CMD="<单行命令>" [SRC=SELF|REPLAY|GATE] [TAIL=15]
# 退出码 = 被执行命令的退出码(失败可见),证据行无论成败都会写入。
set -uo pipefail
CMD="${CMD:-}"; SRC="${SRC:-SELF}"; TAILN="${TAIL:-15}"
[ -n "$CMD" ] || { echo 'usage: make evidence CMD="<command>" [SRC=SELF|REPLAY|GATE] [TAIL=15]' >&2; exit 2; }
case "$SRC" in SELF|REPLAY|GATE) ;; *) echo "ERROR: SRC 须为 SELF|REPLAY|GATE(当前:$SRC)" >&2; exit 2 ;; esac
case "$TAILN" in ''|*[!0-9]*) TAILN=15 ;; esac
case "$CMD" in *$'\n'*) echo 'ERROR: CMD 须为单行命令' >&2; exit 2 ;; esac
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
[ -n "$branch" ] || { echo "ERROR: 不在 git 仓库内" >&2; exit 1; }
slug=$(echo "$branch" | tr '/' '-')
card=".task/${slug}.md"
if [ ! -f "$card" ]; then
# 回退:按卡片「分支:」字段匹配(与 task.sh cmd_sync 同款口径)
for pc in .task/*.md; do
[ -f "$pc" ] || continue
if [ "$(sed -n 's/^- 工作区:.*分支:\([A-Za-z0-9._/-]\+\).*/\1/p' "$pc" | head -1)" = "$branch" ]; then
card=$pc; break
fi
done
fi
[ -f "$card" ] || { echo "ERROR: 找不到分支 ${branch} 的任务卡(.task/${slug}.md)" >&2; exit 1; }
head=$(git rev-parse HEAD)
short_head=$(echo "$head" | cut -c1-10)
ts=$(date -u +%FT%TZ)
tmp=$(mktemp); trap 'rm -f "$tmp"' EXIT
bash -c "$CMD" >"$tmp" 2>&1; rc=$?
digest=$(sha256sum "$tmp" | cut -c1-16)
# 脱敏(baseline 硬边界 2):token 风格的 40 位 hex / Authorization 头不打进任务卡。
sanitize() {
sed -E -e 's/(token[ =:]+)[0-9a-fA-F]{40}/\1<redacted>/g' \
-e 's/(Authorization: *token +)[^ ]+/\1<redacted>/Ig' \
-e 's/(access_token[=:]+)[0-9a-fA-F]{40}/\1<redacted>/Ig'
}
if ! grep -q '^## 证据(Evidence)' "$card"; then
printf '\n## 证据(Evidence)\n\n<!-- make evidence CMD="…" 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->\n| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |\n|---|---|---|---|---|---|\n' >> "$card"
fi
cmd_cell=$(echo "$CMD" | sed -e 's/|/\\|/g' -e 's/`/‘/g')
printf '| %s | `%s` | %s | %d | %s | %s |\n' "$ts" "$cmd_cell" "$short_head" "$rc" "$SRC" "$digest" >> "$card"
printf '\n<!-- evidence:run %s -->\n```\n$ %s\n[exit %d | HEAD %s | sha256:%s] 输出尾部 %s 行:\n%s\n```\n' \
"$ts" "$CMD" "$rc" "$short_head" "$digest" "$TAILN" "$(tail -n "$TAILN" "$tmp" | sanitize)" >> "$card"
echo "[evidence] card=$card cmd=\"${CMD}\" exit=$rc src=$SRC head=$short_head sha256=$digest"
exit "$rc"
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
# 外部事实登记表逐条重验:对 policy/facts.md 每条事实执行其验证命令并报告 PASS/FAIL。
# 规则依据:AGENTS.md「证据与验证门禁」(外部事实零信任);verification.md「声明与证据来源」。
# 用法:make facts-verify [ARGS=--update] (--update:把 PASS 项 last_verified 刷为当天并写回)
# 表内验证命令约定不含竖线;含 "\|" 转义时按字面还原后执行。
set -uo pipefail
F="${FACTS:-policy/facts.md}"
[ -f "$F" ] || { echo "ERROR: 事实登记表 $F 不存在" >&2; exit 1; }
python3 - "$F" "${1:-}" <<'PY'
import re, subprocess, sys, datetime
path, update = sys.argv[1], sys.argv[2] == "--update"
lines = open(path, encoding="utf-8").read().splitlines(keepends=True)
today = datetime.date.today().isoformat()
total = fails = 0
for i, line in enumerate(lines):
if not re.match(r"^\|\s*F\d+\s*\|", line):
continue
raw = line.strip().strip("|").replace("\\|", "\x00")
cells = [c.strip().replace("\x00", "|") for c in raw.split("|")]
if len(cells) < 4:
print(f"[row {i+1}] SKIP 格式不符(列数<4)", file=sys.stderr)
continue
fid, cmd = cells[0], cells[2].strip("`")
total += 1
try:
r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=90)
ok = r.returncode == 0
except subprocess.TimeoutExpired:
ok = False
print(f"[{fid}] TIMEOUT >90s", file=sys.stderr)
print(f"[{fid}] {'PASS' if ok else 'FAIL'} :: {cmd[:80]}")
if not ok:
fails += 1
elif update and len(cells) >= 4 and re.match(r"\d{4}-\d{2}-\d{2}", cells[3]):
cells[3] = today
lines[i] = "| " + " | ".join(c.replace("|", "\\|") for c in cells) + " |\n"
if update:
open(path, "w", encoding="utf-8").writelines(lines)
print(f"[facts-verify] total={total} pass={total - fails} fail={fails}"
+ (";last_verified 已刷新为 " + today if update and total else ""))
sys.exit(1 if fails else 0)
PY
+43 -1
View File
@@ -59,6 +59,34 @@ cmd_precheck() {
fi
done < <(git for-each-ref --format='%(refname:short) %(committerdate:unix)' "refs/remotes/origin/feature/card-close-*")
[ -n "$stale" ] && echo "[cards] 闭合分支超过 24 小时未合入:$(echo $stale)"
# [hooks] 声明≠能力:core.hooksPath 未指向 scripts/hooks 时,"钩子已启用"只是声明。
local hp; hp=$(git config core.hooksPath 2>/dev/null || true)
if [ "$hp" = "scripts/hooks" ] && [ -x scripts/hooks/pre-commit ]; then
echo "[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)"
else
echo "[hooks] WARN core.hooksPath='${hp:-未设置}'——本地钩子未生效,先 make setup;本检出提交将绕过本地闸(远端准入仍在)"
fi
# [facts] 外部事实登记表过期项(policy/facts.md;引用前先跑其验证命令,AGENTS.md 证据与验证门禁)。
if [ -f policy/facts.md ]; then
local fid lv ttl expired="" ftotal=0
while IFS='|' read -r _ fid _ _ lv ttl _; do
fid=$(echo "${fid:-}" | tr -d ' '); case "$fid" in F[0-9]*) ;; *) continue ;; esac
ftotal=$((ftotal+1))
lv=$(echo "$lv" | tr -d ' '); ttl=$(echo "$ttl" | tr -d ' ')
case "$lv" in [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) ;; *) expired="$expired ${fid}(日期格式)"; continue ;; esac
case "$ttl" in ''|*[!0-9]*) ttl=30 ;; esac
if [ $(( $(date +%s) - $(date -d "$lv" +%s) )) -gt $(( ttl*86400 )) ]; then
expired="$expired ${fid}(last=${lv},ttl=${ttl}d)"
fi
done < <(grep -E '^\| *F[0-9]+ ' policy/facts.md || true)
if [ -n "$expired" ]; then
echo "[facts] WARN 过期事实:$(echo $expired)——引用前先 make facts-verify 重验并更新登记表"
else
echo "[facts] policy/facts.md ${ftotal} 条事实均在有效期内"
fi
else
echo "[facts] WARN policy/facts.md 不存在——外部事实登记表缺失(AGENTS.md 证据与验证门禁)"
fi
echo "[required-reading] core/baseline.md, workflows/delivery.md §1$([ "$dirty" -gt 0 ] && echo ", standards/collaboration.md(树脏=他人或未收尾现场,不得写入)")"
echo "[R0] $(echo "$rmain" | cut -c1-10)(完整:$rmain)"
if [ "$dirty" -gt 0 ] || [ "${sub_drift:-0}" -gt 0 ]; then
@@ -120,10 +148,16 @@ cmd_start() {
|---|---|---|---|
| 启动 | ${rmain} | $(git -C "$wt" rev-parse HEAD) | $(date -u +%FT%TZ) |
## 证据(Evidence)
<!-- make evidence CMD="…" 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|---|---|---|---|---|---|
## 结果与验证(完成后填写)
- 结果:
- 验证:
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
- 影响与剩余项:
EOF
git -C "$wt" add .task && git -C "$wt" commit -q -m "Record the task card for ${slug}" && echo "[card] $card(已随分支提交)"
@@ -155,6 +189,14 @@ cmd_sync() {
printf '| 同步前验收 | %s | %s | %s |\n' "$base" "$head" "$(date -u +%FT%TZ)" >> "$card"
git add "$card"
git commit -q -m "Record the sync checkpoint" || true
# [evidence] 同步改变了被测对象:卡片证据表中旧 HEAD 的行自动失效,关键检查须重跑。
local eh stale_ev="" short_head
short_head=$(echo "$head" | cut -c1-10)
while IFS='|' read -r _ _ _ eh _ _ _; do
eh=$(echo "$eh" | tr -d ' `')
[ -n "$eh" ] && [ "$eh" != "$short_head" ] && stale_ev="$stale_ev $eh"
done < <(grep -E '^\| [0-9]{4}-[0-9]{2}-[0-9]{2}T[^|]*\|' "$card" 2>/dev/null || true)
[ -n "$stale_ev" ] && echo "[evidence] WARN 证据表含旧被测 HEAD:$(echo $stale_ev | tr ' ' '\n' | sort -u | tr '\n' ' ')(≠ 当前 $short_head)——同步后关键检查须重跑并以 make evidence 重新写入"
else
echo "[warn] 找不到分支 ${branch} 的任务卡,同步记录未写入" >&2
fi