Add evidence-driven gates: facts registry, evidence tooling, provenance rules #205
@@ -0,0 +1,151 @@
|
||||
# feature/evidence-driven-gates 任务卡
|
||||
|
||||
任务主记录:<PR 链接,task-finish 时填写>;状态:进行中;owner:weichen
|
||||
|
||||
## 基线与授权
|
||||
|
||||
- 权威仓: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 技能文本改造(后续任务)。
|
||||
|
||||
## 同步记录
|
||||
|
||||
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|
||||
|---|---|---|---|
|
||||
| 启动 | 602d2ce9c6900343155f2a9c7ccb4f1c83aa4a59 | 602d2ce9c6900343155f2a9c7ccb4f1c83aa4a59 | 2026-10-05T10:41:39Z |
|
||||
|
||||
## 结果与验证(完成后填写)
|
||||
|
||||
- 结果:guidelines 推进至 0.1.0-draft.10(PR Development-Guidelines#4,merge af199ee,
|
||||
已钉入本 PR gitlink);Dev 仓落地 AGENTS.md「证据与验证门禁」节、policy/facts.md(F1–F4)、
|
||||
scripts/evidence.sh + facts-verify.sh、Makefile evidence/facts-verify 目标、task.sh
|
||||
precheck [hooks]/[facts] + sync [evidence] 告警 + 卡片模板证据表。全部交付,无删减。
|
||||
- 验证:见下方证据表——make test 全量 PASS(boundaries/pins/libs/core/backend/frontend/
|
||||
ui/agent-command,exit 0);precheck READY 含新增 [hooks]/[facts] 行;facts-verify 4/4
|
||||
PASS(F2 曾 FAIL 后修正命令并复验——暴露登记命令与手工命令的口径漂移,属预期机制生效);
|
||||
evidence 工具正例(脱敏 token <redacted>)/负例(exit=1 行照记、rc 透传)自测通过;
|
||||
引号缺陷(make 变量重引号拆坏 CMD)自测发现并修复。NOT_RUN:无。诚实说明:6518f6e 后
|
||||
仅 .task/AGENTS.md/task.sh(sync 告警)变更,均不在 make test 覆盖面,按证据复用规则
|
||||
不重跑全量,另记 bash -n 复检行;PR HEAD 全量由 CI 作 GATE 权威源。
|
||||
- 影响与剩余项:新增两 make 目标与 precheck 两行(向后兼容,旧卡无证据表时 evidence.sh
|
||||
自建段落);遗留(后续任务):task-close 核对 merge SHA 的 CI 状态、review 技能文本改
|
||||
双盲重放、CI-first 完成声明模板。
|
||||
|
||||
## 证据(Evidence)
|
||||
|
||||
<!-- make evidence CMD="…" 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
|
||||
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|
||||
|---|---|---|---|---|---|
|
||||
| 2026-10-05T10:47:37Z | `false` | ba83a17c11 | 1 | SELF | e3b0c44298fc1c14 |
|
||||
|
||||
<!-- evidence:run 2026-10-05T10:47:37Z -->
|
||||
```
|
||||
$ false
|
||||
[exit 1 | HEAD ba83a17c11 | sha256:e3b0c44298fc1c14] 输出尾部 15 行:
|
||||
|
||||
```
|
||||
| 2026-10-05T10:48:16Z | `printf 'token 0123456789abcdef0123456789abcdef01234567 hello\n'` | ba83a17c11 | 0 | SELF | aae6a8955c582b4b |
|
||||
|
||||
<!-- evidence:run 2026-10-05T10:48:16Z -->
|
||||
```
|
||||
$ printf 'token 0123456789abcdef0123456789abcdef01234567 hello\n'
|
||||
[exit 0 | HEAD ba83a17c11 | sha256:aae6a8955c582b4b] 输出尾部 15 行:
|
||||
token <redacted> hello
|
||||
```
|
||||
| 2026-10-05T10:48:31Z | `make precheck` | efa701279f | 0 | SELF | d7a230052e4ac220 |
|
||||
|
||||
<!-- evidence:run 2026-10-05T10:48:31Z -->
|
||||
```
|
||||
$ make precheck
|
||||
[exit 0 | HEAD efa701279f | sha256:d7a230052e4ac220] 输出尾部 15 行:
|
||||
make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-evidence-driven-gates'
|
||||
[repo] branch=feature/evidence-driven-gates head=efa701279f
|
||||
[remote] origin/main=602d2ce9c6 ahead=3 behind=0
|
||||
[tree] clean submodules_drift=0 uninit=0
|
||||
[worktrees] linked=1
|
||||
[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)
|
||||
[facts] policy/facts.md 4 条事实均在有效期内
|
||||
[required-reading] core/baseline.md, workflows/delivery.md §1
|
||||
[R0] 602d2ce9c6(完整:602d2ce9c6900343155f2a9c7ccb4f1c83aa4a59)
|
||||
[verdict] READY — 可建任务工作区:make task-start NAME=feature/your-branch
|
||||
make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-evidence-driven-gates'
|
||||
```
|
||||
| 2026-10-05T10:48:46Z | `make facts-verify` | efa701279f | 2 | SELF | 6c61e644395042fa |
|
||||
|
||||
<!-- evidence:run 2026-10-05T10:48:46Z -->
|
||||
```
|
||||
$ make facts-verify
|
||||
[exit 2 | HEAD efa701279f | sha256:6c61e644395042fa] 输出尾部 15 行:
|
||||
make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-evidence-driven-gates'
|
||||
[F1] PASS :: curl -fsS -m 10 https://git-home.u1680832.nyat.app:30319/api/v1/version
|
||||
[F2] FAIL :: t=$(sed -n "s/^ACCESS_TOKEN=//p" ~/.secrets/gitea); test -n "$t" && curl -fsS -m
|
||||
[F3] PASS :: GIT_SSH_COMMAND="ssh -i ~/.ssh/home_gitea -o BatchMode=yes" git ls-remote ssh://
|
||||
[F4] PASS :: ! curl -fsS -m 8 https://git-panel.u1680832.nyat.app:30319/api/v1/version
|
||||
[facts-verify] total=4 pass=3 fail=1
|
||||
make[1]: *** [Makefile:52: facts-verify] Error 1
|
||||
make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-evidence-driven-gates'
|
||||
```
|
||||
| 2026-10-05T10:49:21Z | `make facts-verify` | efa701279f | 0 | SELF | 919401b838d77909 |
|
||||
|
||||
<!-- evidence:run 2026-10-05T10:49:21Z -->
|
||||
```
|
||||
$ make facts-verify
|
||||
[exit 0 | HEAD efa701279f | sha256:919401b838d77909] 输出尾部 15 行:
|
||||
make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-evidence-driven-gates'
|
||||
[F1] PASS :: curl -fsS -m 10 https://git-home.u1680832.nyat.app:30319/api/v1/version
|
||||
[F2] PASS :: t=$(sed -n "s/^[ -]*ACCESS_TOKEN=//p" ~/.secrets/gitea); test -n "$t" && curl -f
|
||||
[F3] PASS :: GIT_SSH_COMMAND="ssh -i ~/.ssh/home_gitea -o BatchMode=yes" git ls-remote ssh://
|
||||
[F4] PASS :: ! curl -fsS -m 8 https://git-panel.u1680832.nyat.app:30319/api/v1/version
|
||||
[facts-verify] total=4 pass=4 fail=0
|
||||
make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-evidence-driven-gates'
|
||||
```
|
||||
| 2026-10-05T10:49:32Z | `make test` | 6518f6ed0a | 0 | SELF | 751ae7dccd018ce7 |
|
||||
|
||||
<!-- evidence:run 2026-10-05T10:49:32Z -->
|
||||
```
|
||||
$ make test
|
||||
[exit 0 | HEAD 6518f6ed0a | sha256:751ae7dccd018ce7] 输出尾部 15 行:
|
||||
✓ test/card-anchor.test.tsx (4 tests) 91ms
|
||||
✓ test/file-preview.test.tsx (9 tests) 77ms
|
||||
✓ test/inline-confirm.test.tsx (7 tests) 94ms
|
||||
✓ test/capability.test.tsx (14 tests) 145ms
|
||||
|
||||
Test Files 14 passed (14)
|
||||
Tests 103 passed (103)
|
||||
Start at 10:50:04
|
||||
Duration 612ms (transform 373ms, setup 0ms, import 1.36s, tests 874ms, environment 3.22s)
|
||||
|
||||
knip unused exports 55 <= 55
|
||||
cd deps/agent-command && go vet ./... && go test -count=1 ./...
|
||||
ok agent-command 0.002s
|
||||
ok agent-command/conformance 0.002s
|
||||
make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-evidence-driven-gates'
|
||||
```
|
||||
| 2026-10-05T10:52:17Z | `bash -n scripts/task.sh && bash -n scripts/evidence.sh && bash -n scripts/facts-verify.sh && bash scripts/check-pins.sh >/dev/null && bash scripts/lint-boundaries.sh >/dev/null && echo 'final-HEAD quick checks OK'` | 9337207906 | 0 | SELF | 944a991897d9f475 |
|
||||
|
||||
<!-- evidence:run 2026-10-05T10:52:17Z -->
|
||||
```
|
||||
$ bash -n scripts/task.sh && bash -n scripts/evidence.sh && bash -n scripts/facts-verify.sh && bash scripts/check-pins.sh >/dev/null && bash scripts/lint-boundaries.sh >/dev/null && echo 'final-HEAD quick checks OK'
|
||||
[exit 0 | HEAD 9337207906 | sha256:944a991897d9f475] 输出尾部 15 行:
|
||||
warn: deps/agent-native deps/development-guidelines is 26b562d2e34f, Dev is af199eeaa23c
|
||||
warn: deps/agent-squad deps/development-guidelines is 26b562d2e34f, Dev is af199eeaa23c
|
||||
warn: deps/agent-intent deps/development-guidelines is 26b562d2e34f, Dev is af199eeaa23c
|
||||
final-HEAD quick checks OK
|
||||
```
|
||||
| 同步前验收 | 602d2ce9c6900343155f2a9c7ccb4f1c83aa4a59 | e1009f475fd203619a1fd81cf2763b9c4c01e9a2 | 2026-10-05T10:52:23Z |
|
||||
@@ -24,8 +24,8 @@
|
||||
允许的堆叠例外):任务卡显式记录父任务与父 PR;子分支命名 `<type>/<父slug>-<子题>`,
|
||||
不嵌 SHA;合入顺序固定父→子;父合入后子分支须 `make task-sync` 并入最新 main 并把 PR
|
||||
base 改回 main,方可声明可合入(分支保护要求 head 含 origin/main,未同步必被拦)。
|
||||
- **验收、请求审核、合入前**各运行一次 `make task-sync`;完成声明附 precheck、task-sync、
|
||||
测试三段输出。
|
||||
- **验收、请求审核、合入前**各运行一次 `make task-sync`;完成声明的 precheck、task-sync、
|
||||
测试三段输出由 `make evidence` 写入任务卡(见「证据与验证门禁」),不手抄。
|
||||
- 合入后**在 worktree 内**运行 `make task-finish` 回收 worktree 与分支(脚本会把
|
||||
当前目录当 worktree 回收——在主检出运行会误删主检出,已加防护);放弃未合入
|
||||
工作用 `make task-finish ABANDON="--abandon <原因>"` 并记录原因。
|
||||
@@ -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 一律经 `CMD="<命令>" make evidence` 写入
|
||||
(自动记录命令、被测 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 握手失败),
|
||||
见到一律按过期处理,不要再试。当前权威通道:
|
||||
|
||||
@@ -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「证据与验证门禁」)。
|
||||
# 用法:CMD="<单行命令>" make evidence [SRC=SELF|REPLAY|GATE] [TAIL=15];退出码随被执行命令。
|
||||
# CMD 经环境直传(不经 make 变量展开重引号,命令内可含引号/管道);SRC/TAIL 为简单词,走 make 变量。
|
||||
evidence:
|
||||
@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→静态链接)"
|
||||
|
||||
Vendored
+1
-1
Submodule deps/development-guidelines updated: ed2af3e604...af199eeaa2
@@ -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,文件为 markdown 列表格式 `- ACCESS_TOKEN=…`,不入仓不回显) | `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/<Repo>.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 |
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
# 证据写入工具:执行命令,把(命令、被测 HEAD、退出码、输出 sha256、尾部摘录、时间、来源)
|
||||
# 追加到当前分支任务卡的「## 证据(Evidence)」表。规范依据:AGENTS.md「证据与验证门禁」、
|
||||
# guidelines standards/verification.md「声明与证据来源」——转写/手抄的输出视为无效证据。
|
||||
# 用法:CMD="<单行命令>" make evidence [SRC=SELF|REPLAY|GATE] [TAIL=15]
|
||||
# (CMD 经环境变量直传;也可直接 CMD=… SRC=… TAIL=… bash scripts/evidence.sh)
|
||||
# 退出码 = 被执行命令的退出码(失败可见),证据行无论成败都会写入。
|
||||
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"
|
||||
Executable
+42
@@ -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
|
||||
+45
-1
@@ -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)
|
||||
|
||||
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(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,16 @@ 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 的行,其后若有非 .task 变更则失效须重跑。
|
||||
local eh stale_ev="" short_head changed
|
||||
short_head=$(echo "$head" | cut -c1-10)
|
||||
while IFS='|' read -r _ _ _ eh _ _ _; do
|
||||
eh=$(echo "$eh" | tr -d ' `')
|
||||
{ [ -n "$eh" ] && [ "$eh" != "$short_head" ]; } || continue
|
||||
changed=$(git diff --name-only "${eh}" HEAD -- . ':(exclude).task' 2>/dev/null | head -1)
|
||||
[ -n "$changed" ] && 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
|
||||
|
||||
Reference in New Issue
Block a user