Merge authoritative main into the task branch

Why: include the current integration baseline before verification.

Evidence: fetched origin/main=a4311c6341c318d4bbf1563b3973358326086794.

Impact: integrate upstream changes without rewriting task history.

Refs: task-sync
This commit is contained in:
2026-10-22 02:10:05 +00:00
21 changed files with 2182 additions and 36 deletions
+34 -6
View File
@@ -443,16 +443,44 @@ jobs:
git -C rdm submodule update --init --recursive
done
echo "RDM commit: $(git -C rdm rev-parse HEAD)"
# 凭据保留到构建循环内的 submodule update;make 经 without_creds
# 剥离,避免 token 进入构建进程。不写凭据文件、不把 token 放进命令行。
without_creds() {
env -u GIT_CONFIG_COUNT \
-u GIT_CONFIG_KEY_0 -u GIT_CONFIG_VALUE_0 \
-u GIT_CONFIG_KEY_1 -u GIT_CONFIG_VALUE_1 \
-u GIT_CONFIG_KEY_2 -u GIT_CONFIG_VALUE_2 \
-u GIT_CONFIG_KEY_3 -u GIT_CONFIG_VALUE_3 \
-u SUBMODULE_TOKEN \
"$@"
}
: > rdm/pin-build-digests.txt
for rev in "${pins[@]}"; do
git -C rdm checkout --detach "$rev"
git -C rdm submodule update --init --recursive
# 不能用「有无 go.mod」判定落地:pin 集合混有 Go 仓与非 Go 仓
#(例如前端仓根目录没有 go.mod)。真正要保证的是每个 pins/*
# gitlink 子模块 HEAD 等于记录的提交,且工作树已有已跟踪文件——
# 否则 Go 可能静默回退到远端解析。
while read -r mode _type gitlink path; do
[ "$mode" = "160000" ] || continue
head="$(git -C rdm -C "$path" rev-parse HEAD)"
if [ "$head" != "$gitlink" ]; then
echo "rdm pin ${rev} 的 ${path} 未落地:HEAD 不等于 gitlink 提交" >&2
exit 1
fi
if [ -z "$(git -C rdm -C "$path" ls-files)" ]; then
echo "rdm pin ${rev} 的 ${path} 未落地:工作树无已跟踪文件" >&2
exit 1
fi
done < <(git -C rdm ls-tree "$rev" pins/)
without_creds make -C rdm build
sha256sum rdm/bin/agent-system-rdm | awk -v rev="$rev" '{print rev, $1}' >> rdm/pin-build-digests.txt
done
unset GIT_CONFIG_COUNT GIT_CONFIG_KEY_0 GIT_CONFIG_VALUE_0 \
GIT_CONFIG_KEY_1 GIT_CONFIG_VALUE_1 GIT_CONFIG_KEY_2 \
GIT_CONFIG_VALUE_2 GIT_CONFIG_KEY_3 GIT_CONFIG_VALUE_3
unset SUBMODULE_TOKEN
: > rdm/pin-build-digests.txt
for rev in "${pins[@]}"; do
git -C rdm checkout --detach "$rev"
make -C rdm build
sha256sum rdm/bin/agent-system-rdm | awk -v rev="$rev" '{print rev, $1}' >> rdm/pin-build-digests.txt
done
- name: Install Node 22
run: |
set -euo pipefail
@@ -0,0 +1,184 @@
# feature/agent-stack-audit-remediation-b1 任务卡
任务主记录:PR #685 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/685;状态:已完成(合入前闭合);owner:weichen
## 基线与授权
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= d9e9c0560a2767539f4415b579adbeff86a22ba6
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-agent-stack-audit-remediation-b1(worktree);分支:feature/agent-stack-audit-remediation-b1
- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=14019d085070900db07365351975abf0915c3b1fc34ef0d05c85fe975c9ac834;triage_receipt=/tmp/triage-remediation-b1-1792595736.json;receipt_sha256=0de1f5ad5737d0839f79d7098faced9f6f9366ca1a17486083b8439749dea27e
- 授权与交付终点:用户已批准按方案实施第一批(U1-U7、U10)。范围内连续推进:agent-per-unit 逐单元实施、host 权威验证、组件分支提交、Backend 嵌套 gitlink 与 Dev 组合指针更新、组合验证、独立 Review、PR/CI/合入。终点为第一批全部单元收口。未授权:部署、真实数据迁移、发布、第二批 U8/U9/U11、功能增量 A1-A4、A2A 互通。
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
- 人工决策点:仅限目标/范围/验收变更、重大取舍、高后果操作或无法自动判定的业务验收
- 当前阶段:组合验证与交付;当前增量:第一批 U1-U7、U10 全部实施完成
- host 记录:为使 U7 可按 agent-per-unit 执行,deps/agent-command 缺少组件级 AGENT-EXECUTION.md(其余四个目标仓均有且 validate: ok),host 按 agent-intent 同版模板补齐并以执行配置身份提交到该组件任务分支;理由与差异记录于本卡。
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
## 目标与非目标
**目标**:按已批准方案(docs/superpowers/plans/2026-10-21-agent-stack-audit-remediation-plan.md,基线 d9e9c0560a)修复第一批 F01-F10、F13:执行隔离(U1/U2)、失败结果真实语义与恢复安全(U3)、权限缓存版本化(U4)、意图快照与生成校验(U5)、Backend 意图受理(U6)、命令隔离与回执(U7)、Backend 用户/项目配置路由(U10)。每单元以原触发条件回归阻断为验收,正常路径不退化。
**非目标**:U8/U9/U11(第二批);A1-A4 功能增量;A2A 互通(F16);全库接口替换、幂等平台、分布式执行;部署/迁移/发布;不把工作区路径检查宣称为 OS 沙箱。
## Agent 实施收据
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
单元包由 host 写于 /tmp/agent-stack-b1/<unit>.json(network=false、external_cost=false 均为声明);波次:A=U1(native)+U3(squad)+U5(intent) 并行;B=U2(native)+U4(squad)+U10(backend);C=U7a(command)+U6(backend,依赖 U5 嵌套指针);D=U7b(backend cmdbus,依赖 U7a)。同仓单元串行 apply。
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|---|
| agent-stack-b1-u1-native-workspace | native: adapters/native/backend.go+test, runtime/tools/{exec,jail,atomic}.go+test | digest 见 /tmp/agent-stack-b1/u1-run.json | implementation | [cursor, antigravity, pi-glm, pi, codex] | 1 | cursor-agent-stable | success | refs/agent-exec/e10eca84…/18a9b3caba61 → native 81e5bf3(transport 273213a 重写为规范提交) | 否 | /tmp/agent-stack-b1/u1-run.json |
| agent-stack-b1-u5-intent-snapshot-generated | intent: intent/(fold/registry/catalog/recognizer/slots/generator+tests) | digest 见 /tmp/agent-stack-b1/u5-run.json | implementation | 同上 | 1 | cursor-agent-stable | success | refs/agent-exec/357b5d70…/90a2c1b0488f → intent 765a7e7 | 否 | /tmp/agent-stack-b1/u5-run.json |
| agent-stack-b1-u7a-command-isolation | command: registry.go/result.go/conformance/+tests | digest 见 /tmp/agent-stack-b1/u7a-run.json | implementation | 同上 | 1 | cursor-agent-stable | success | refs/agent-exec/243aef44…/2e0a169b7174 → command fe7987d | 否 | /tmp/agent-stack-b1/u7a-run.json |
| u2-native-session-contract | native: contract.go/backend.go/session/tree.go/tools+regression tests | digest 见 /tmp/agent-stack-b1/u2-run.json | implementation | 同上 | 1 | cursor-agent-stable | success | transport f7a3ff39 重写为规范提交(基于 81e5bf3) | 否 | /tmp/agent-stack-b1/u2-run.json |
| u4-squad-resolver-config | squad: squad/backend.go+config_identity.go, adapters/native/register.go+tests | digest 见 /tmp/agent-stack-b1/u4-run.json | implementation | 同上 | 1 | cursor-agent-stable | success | squad 3a98d873 → aa15a5d 规范提交 | 否 | /tmp/agent-stack-b1/u4-run.json |
| u10-backend-scoped-config(首次) | backend: internal/app|runtime|squadadapter | digest 见 /tmp/agent-stack-b1/u10-run.json | implementation | [pi, codex](cursor/agy 临时禁用) | 1 | pi MiniMax-M3 | failed-unknown(fail-stop:退出码 0 但无有效终态记录) | 无 transport,不采用 | 否 | /tmp/agent-stack-b1/u10-run.json;现场保留 /tmp/agent-exec-1000/b627ce2c41c90bea0d9f78270819d419 待对账 |
| u10-backend-scoped-config(重派) | 同上;包修正:allowed_paths 补嵌套 deps gitlink,禁止 -modfile 变通 | digest 见 /tmp/agent-stack-b1/u10b-run.json | implementation | [pi, codex] | 1 | pi MiniMax-M3 | success | transport 4c08cd8 → backend 80faf3e 规范提交 | 否 | /tmp/agent-stack-b1/u10b-run.json |
| u3a-native-result-fidelity | native: exec/、adapters/native/、runtime/loop.go | digest 见 /tmp/agent-stack-b1/u3a-run.json | implementation | 同上 | 1 | cursor-agent-stable | success | transport bf3e151 → native 5c0d2d8 规范提交 | 否 | /tmp/agent-stack-b1/u3a-run.json |
| (随 U10 重派)嵌套 agent-intent 指针 765a7e7 | backend deps/agent-intent bump | host 提交 406cffd | host | — | — | host | done | 406cffd | host 直接提交(gitlink 集成) | — | — |
| u6-backend-intent-admission | backend: intentgate/route/server/workflow | digest 见 /tmp/agent-stack-b1/u6-run.json | implementation | [pi, codex] | 1 | pi MiniMax-M3 | success | transport 0dff5ee → backend 0e55875 规范提交 | 否 | /tmp/agent-stack-b1/u6-run.json |
| (随 U6)嵌套 agent-command 指针 fe7987d | backend deps/agent-command bump | host 提交 e8d0c9b | host | — | — | host | done | e8d0c9b | host 直接提交(gitlink 集成) | — | — |
| u3b-squad-result-recovery(首次) | squad: squad/、adapters/native/ | digest 见 /tmp/agent-stack-b1/u3b-run.json | implementation | 同上 | 1 | cursor-agent-stable | failed-unknown(fail-stop;cursor TLS 会话前故障,553B 日志、零改动,现场 /tmp/agent-exec-1000/287d14d44c37c0336519417b92c5cc5a 保留) | 无 | 否 | /tmp/agent-stack-b1/u3b-run.json;对账:日志证明未建立会话、workspace 零改动,环境瞬断,重派 |
| u3b-squad-result-recovery(重派) | 同上 | digest 见 /tmp/agent-stack-b1/u3b2-run.json | implementation | [cursor(后禁用), antigravity(后禁用), pi, codex] | 2 | cursor-agent-stable | failed-unknown("Cannot use this model: auto",48B 日志、零改动;与已记录配额耗尽一致)→ 禁用 cursor(squad 4e65577) | 无 | 否 | /tmp/agent-stack-b1/u3b2-run.json;现场 /tmp/agent-exec-1000/b7ed6a71… |
| u3b(第 3 次) | 同上 | digest 见 /tmp/agent-stack-b1/u3b3-run.json | implementation | [antigravity, pi, codex] | 1 | agy gemini | failed-unknown(400 FAILED_PRECONDITION 地区限制,0 token、零改动)→ 禁用 agy(squad 后续提交) | 无 | 否 | /tmp/agent-stack-b1/u3b3-run.json;现场 /tmp/agent-exec-1000/8fd18fc9… |
| u7b-backend-receipts | backend: internal/cmdbus、internal/server | digest 见 /tmp/agent-stack-b1/u7b-run.json | implementation | [pi, codex] | 1 | pi MiniMax-M3 | success | transport 3a2b548 → backend 4030449 规范提交 | 否 | /tmp/agent-stack-b1/u7b-run.json |
| u3b(第 4 次) | 同上 | digest 见 /tmp/agent-stack-b1/u3b4-run.json | implementation | [pi, codex] | 1 | pi MiniMax-M3 | success | transport 5a00e3c → squad 23f161b 规范提交 | 否 | /tmp/agent-stack-b1/u3b4-run.json |
| u6-repair-boundary | backend: server 测试改经 intentgate | digest 见 /tmp/agent-stack-b1/u6r-run.json | repair | [pi, codex] | 1 | pi MiniMax-M3 | success | backend 49c1259 | 否 | /tmp/agent-stack-b1/u6r-run.json |
| u3b-repair-sqlite-test(首次) | squad: adapters/sqlite 测试 | digest 见 /tmp/agent-stack-b1/u3br-run.json | repair | 同上 | 1 | pi MiniMax-M3 | blocked(实现完成但验证环境缺嵌套 deps,包修正后重派;现场保留 b0f0f936…) | 无 | 否 | /tmp/agent-stack-b1/u3br-run.json |
| u3b-repair-sqlite-test(重派) | 同上;包补嵌套 deps | digest 见 /tmp/agent-stack-b1/u3br2-run.json | repair | [pi, codex] | 1 | pi MiniMax-M3 | success | squad 0f1daa8 | 否 | /tmp/agent-stack-b1/u3br2-run.json |
| gitlink-integration 1-4 | Dev 顶层/嵌套指针级联对齐 | host 集成 | implementation | [cursor,…] | 各 1 | cursor / antigravity? / pi | success×4 | 76766356/d31c7b55/3ae7605f/647fa51c | 否 | /tmp/agent-stack-b1/gitlink{,2,3,4}-run.json |
## 关联方向与规划
- 适用状态:待核对(无适用规划时说明原因)
- 主记录路径/版本:待核对(路径按仓库根目录定位,归档后不变;只引用唯一正文)
- 对应里程碑:待核对
## 需求分析
| ID | 描述 | 项目符合度及依据 | 优先级 | 验收条件 | 依赖 | 结论 |
|---|---|---|---|---|---|---|
| R1-R7 | 方案 Requirements 节(执行与权限/输入恢复可核对性) | 与方案一致,逐条映射到 U1-U7、U10 | P1 | 方案各单元验收行 | U2←U1,U6←U5,U7b←U7a | 已确认 |
## 方案与计划
- 方案引用:docs/superpowers/plans/2026-10-21-agent-stack-audit-remediation-plan.md(本分支 d3161347,审计基线 d9e9c0560a)
- 实施计划引用:本卡「Agent 实施收据」节前导说明(波次与依赖)
- 测试计划引用:方案 Verification Contract;各单元先用 F-ID 触发条件做最小回归,最终候选执行组合验证
## 阶段记录
| 阶段 | 对象版本 | 状态 | 决策/执行者 | 时间 | 依据 |
|---|---|---|---|---|---|
| 需求拆解 | 本卡 | 已完成 | host | 2026-10-21 | 用户批准第一批 |
| 需求评估 | 分流收据 | 已完成 | host | 2026-10-21 | verdict=NEW |
| 需求表确认 | 本卡 R1-R7 | 已完成 | host | 2026-10-21 | 方案 Requirements |
| 方案设计 | 方案文档 d3161347 | 已完成 | 前序会话 | 2026-10-21 | F01-F16 |
| 实施计划 | 本卡波次表 | 已完成 | host | 2026-10-21 | U1-U7/U10 依赖 |
| 实施与自检 | 全部 10 单元 + 2 修复完成;组合 make test PASS(verify-combo-make-test4.json exit=0) | 已完成 | agent-per-unit + host 验证 | 2026-10-21 | verify-*.json 全部 exit=0 |
| 正式测试与验收 | Dev 76766356→647fa51c 组合 | PASS | host | 2026-10-21 | make test(worktree/governance/boundaries/pins/libs/core/backend/frontend/ui/agentcmd) |
| 代码 Review | 待定 | 待执行 | — | — | — |
| 合入 | 待定 | 待执行 | — | — | — |
| 合入核验与收口 | 待定 | 待执行 | — | — | — |
## 探索试验与迭代
| 增量/试验 | 假设 | 范围 | 判断和停止条件 | 授权/决策依据 | 结果 | 变更影响 | 需重审补测 |
|---|---|---|---|---|---|---|---|
| 待定义 | — | — | — | 待核对 | NOT_RUN | 待评估 | 待评估 |
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|---|---|---|---|
| 启动 | d9e9c0560a2767539f4415b579adbeff86a22ba6 | d9e9c0560a2767539f4415b579adbeff86a22ba6 | 2026-10-21T15:17:41Z |
## 证据(Evidence)
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|---|---|---|---|---|---|
## 交付调度
- 调度状态:未入队;调度记录:无
- entry_id:无;attempt_id:无;窗口执行者:无
- 固定候选:无(base/head、组件与相关配置引用实际尝试)
- 调度依据:policy/delivery.md#合入调度
- 固定后证据:源码外 RECEIPT;实际结果后按授权原样导入和闭合
## 结果与验证
- 结果:
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
- 影响与剩余项:
## 规划进展与下一步
- 本次推进/剩余:待核对(依据实际成果更新适用方案/规划)
- 首选下一任务:待核对(无适用规划、已完成或受阻时如实说明,不虚构任务)
- 推荐依据:待填写
- 前置条件/待确认:待核对
- 预期产物/验收条件:待定义
- 重复任务核对:待核对(已有/在途任务、PR 与规划记录)
- 启动授权:未授权(建议不自动启动新任务,仍须明确授权)
## 合入与清理
- Review 记录:独立 CLI 评审单元 agent-stack-b1-independent-review(pi-glm attempt1 success,收据 /tmp/agent-stack-b1/review4-run.json);结论 APPROVE(0 blocker / 0 major / 5 minor 非阻塞跟进项);报告 .task/reviews/2026-10-21-agent-stack-b1-independent-review.md;被审对象 d55e57dd vs main b25846d2
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
- CI 记录:f23d1df7 首 轮三 job Checkout 失败——五个组件 pin 仅存本地;已推送组件分支 fix/agent-stack-audit-b1 至各自 origin(native ac91b84 / squad 0f1daa8 / intent 765a7e7 / command fe7987d / backend b614180),本提交重触发 CI
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-21T15:16:36.646637+00:00",
"command": "make precheck",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev",
"before_head": "d9e9c0560a2767539f4415b579adbeff86a22ba6",
"after_head": "d9e9c0560a2767539f4415b579adbeff86a22ba6",
"branch": "main",
"target_branch": "main",
"target_head": "d9e9c0560a2767539f4415b579adbeff86a22ba6",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
"[repo] branch=main head=d9e9c0560a",
"[remote] origin/main=d9e9c0560a ahead=0 behind=0",
"[tree] clean submodules_drift=0 uninit=0",
"[worktrees] linked=5",
"[recycle] merged un-recycled branches: fix/ci-rdm-pin-materialization",
"[initiatives] local cards (not authoritative): none",
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
"[facts] policy/facts.md 6 条事实均在有效期内",
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
"[R0] d9e9c0560a(完整:d9e9c0560a2767539f4415b579adbeff86a22ba6)",
"[worktree] before-sync: recursive component checks passed",
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
],
"output_sha256": "774b7616a7c39f1933001f482966329eebfb9b7ba3983a29959d38392265dac6",
"receipt_sha256": "282b6b21a2d7d06db1ac80d250f73d0ddfe9dedc917ac12c83a2af0e5e692e9b",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/282b6b21a2d7d06db1ac80d250f73d0ddfe9dedc917ac12c83a2af0e5e692e9b.json"
}
```
## 闭合记录(合入前折叠)
- 本卡在 PR #685(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/685)合入前闭合并归档,随该 PR 一同合入。
- 该 PR 的合并提交即本任务的落地记录。
- 合入后无需回填,也无需单独的闭合 PR。
@@ -0,0 +1,163 @@
# feature/genui-prod-ops-docsync 任务卡
任务主记录:PR #677 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/677;状态:已完成(合入前闭合);owner:weichen
## 基线与授权
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 444fb1dee2a72452be23b97cbaaad9e28c42dffd
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-genui-prod-ops-docsync(worktree);分支:feature/genui-prod-ops-docsync
- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=19d3cdc801db7908de195f7e5ee2b8461c25c3ffcda23bc58aa2d95fb1c156bc;triage_receipt=/tmp/genui-prod-docsync/triage.json;receipt_sha256=e638023ee64c99d4ae8fe90a87ff285d91fcdd60ddb4a19cd252f3c0261a0d03
- 授权与交付终点:用户 2026-10-21「做」(迁 svc 管理 + 收口稿小修);方式裁定「只迁 pg1」。终点:Documents #118 与本 PR 合入、任务收口。未授权:prod 升级/schema 迁移。
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
- 人工决策点:仅限目标/范围/验收变更、重大取舍、高后果操作或无法自动判定的业务验收
- 当前阶段:合入;当前增量:I1(文档同步 + gitlink)
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
## 目标与非目标
- 目标:GenUI 收口稿(Documents `docs/proposals/2026-10-12-generative-ui-usage-and-evaluation.md`)「部署与运行验收」条目与现状一致;Dev 锁定该 Documents 版本。
- 仓外运维(不入仓,本卡只记事实):2026-10-21 `pg1` 由保留目录迁入主检出 `runtime/pg1`,经 `svc_manifest.py bulk-set` 显式重锚数据根后 `scripts/svc.sh pg1 start` 接管;prod(`dev@b4649d7`,无 identity 能力,svc 启动门禁拒绝)留保留目录,新增仓外 `prodctl.sh`(`env -i` + `AGENT_SYSTEM_DSN`)手工启停。
- 非目标:升级 prod / schema v25→v51 迁移(用户选「只迁 pg1」);svc.sh 代码改动。
## Agent 实施收据
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|---|
| documents-genui-prod-ops-docsync | Documents 收口稿单条目 | AGENT-EXECUTION.md | implementation | cursor→antigravity→pi-glm→pi→codex | 1 | cursor / auto(fresh process) | success | applied → Documents 244477e(#118) | 无 | /tmp/genui-prod-docsync/docs-run.json |
| dev-genui-prod-ops-docsync-repin | modules/documents gitlink | AGENT-EXECUTION.md | implementation | cursor→antigravity→pi-glm→pi→codex | 1 | cursor / auto(fresh process) | success | applied → 3289c81 | 无 | /tmp/genui-prod-docsync/dev-run.json |
## 关联方向与规划
- 适用状态:待核对(无适用规划时说明原因)
- 主记录路径/版本:待核对(路径按仓库根目录定位,归档后不变;只引用唯一正文)
- 对应里程碑:待核对
## 需求分析
| ID | 描述 | 项目符合度及依据 | 优先级 | 验收条件 | 依赖 | 结论 |
|---|---|---|---|---|---|---|
| R1 | 待填写 | 待评估 | 待定 | 待定义 | 待确认 | 待评审 |
## 方案与计划
- 方案引用:待填写(含对象版本)
- 实施计划引用:待填写(含增量与需求 ID)
- 测试计划引用:待填写(含需求 ID 与验收条件)
## 阶段记录
| 阶段 | 对象版本 | 状态 | 决策/执行者 | 时间 | 依据 |
|---|---|---|---|---|---|
| 需求拆解 | 待定 | 待执行 | — | — | — |
| 需求评估 | 待定 | 待执行 | — | — | — |
| 需求表确认 | 待定 | 待执行 | — | — | — |
| 方案设计 | 待定 | 待执行 | — | — | — |
| 实施计划 | 待定 | 待执行 | — | — | — |
| 实施与自检 | 待定 | 待执行 | — | — | — |
| 正式测试与验收 | 待定 | 待执行 | — | — | — |
| 代码 Review | 待定 | 待执行 | — | — | — |
| 合入 | 待定 | 待执行 | — | — | — |
| 合入核验与收口 | 待定 | 待执行 | — | — | — |
## 探索试验与迭代
| 增量/试验 | 假设 | 范围 | 判断和停止条件 | 授权/决策依据 | 结果 | 变更影响 | 需重审补测 |
|---|---|---|---|---|---|---|---|
| 待定义 | — | — | — | 待核对 | NOT_RUN | 待评估 | 待评估 |
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|---|---|---|---|
| 启动 | 444fb1dee2a72452be23b97cbaaad9e28c42dffd | 444fb1dee2a72452be23b97cbaaad9e28c42dffd | 2026-10-21T10:27:03Z |
## 证据(Evidence)
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|---|---|---|---|---|---|
## 交付调度
- 调度状态:未入队;调度记录:无
- entry_id:无;attempt_id:无;窗口执行者:无
- 固定候选:无(base/head、组件与相关配置引用实际尝试)
- 调度依据:policy/delivery.md#合入调度
- 固定后证据:源码外 RECEIPT;实际结果后按授权原样导入和闭合
## 结果与验证
- 结果:Documents #118 合入(merge fcea466,条目改为 `dev@b4649d7`、prodctl.sh 手工启停、pg1 由 svc 管理、升级须另行授权);`modules/documents` 82b4619→fcea466(同时带入已合入的 Documents #117 IAM 记录)。
- 验证:Documents #118 head 244477e CI governance=success(GATE,ci_wait CI_SUCCESS);Dev 以本 PR 当前 HEAD 的 CI 为准。仓外运维 SELF:`svc.sh list` 显示 pg1 运行中;prod `/readyz`=200、argv 无 DSN、进程环境仅 HOME/PATH/LANG/AGENT_SYSTEM_DSN。
- 影响与剩余项:仅文档与 gitlink。切换停机约 2 分钟(首启继承调用方 `PGOPTIONS` 只读护栏失败,prodctl 改 `env -i` 后恢复)。prod 升级非本卡范围。
## 规划进展与下一步
- 本次推进/剩余:待核对(依据实际成果更新适用方案/规划)
- 首选下一任务:待核对(无适用规划、已完成或受阻时如实说明,不虚构任务)
- 推荐依据:待填写
- 前置条件/待确认:待核对
- 预期产物/验收条件:待定义
- 重复任务核对:待核对(已有/在途任务、PR 与规划记录)
- 启动授权:未授权(建议不自动启动新任务,仍须明确授权)
## 合入与清理
- Review 记录:NOT_RUN(审阅智能体/平台身份、base/head、结论、问题闭合及重放依据)
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-21T10:26:25.848135+00:00",
"command": "make precheck",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev",
"before_head": "444fb1dee2a72452be23b97cbaaad9e28c42dffd",
"after_head": "444fb1dee2a72452be23b97cbaaad9e28c42dffd",
"branch": "main",
"target_branch": "main",
"target_head": "444fb1dee2a72452be23b97cbaaad9e28c42dffd",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
"[repo] branch=main head=444fb1dee2",
"[remote] origin/main=444fb1dee2 ahead=0 behind=0",
"[tree] clean submodules_drift=0 uninit=0",
"[worktrees] linked=3",
"[recycle] merged un-recycled branches: feature/card-close-20261021-085838Z",
"[initiatives] local cards (not authoritative): none",
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
"[facts] policy/facts.md 6 条事实均在有效期内",
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
"[R0] 444fb1dee2(完整:444fb1dee2a72452be23b97cbaaad9e28c42dffd)",
"[worktree] before-sync: recursive component checks passed",
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
],
"output_sha256": "ea54f9121123c1a453cf9e1c66c4a733828a7166ed7fc26cdd9a02eb23e8c2e3",
"receipt_sha256": "825a20b8f4d5d920996bbaa0d30eccc9a4718c7ae9b5f1400b244d398e90495d",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/825a20b8f4d5d920996bbaa0d30eccc9a4718c7ae9b5f1400b244d398e90495d.json"
}
```
## 闭合记录(合入前折叠)
- 本卡在 PR #677(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/677)合入前闭合并归档,随该 PR 一同合入。
- 该 PR 的合并提交即本任务的落地记录。
- 合入后无需回填,也无需单独的闭合 PR。
| 同步(记录前对象) | 096b74b4775e81cfb2864f98cae2e9fc9d9c1a44 | da2492de25ad6dcbf265a55a4246f088642489cf | 2026-10-21T11:40:43Z |
@@ -0,0 +1,161 @@
# feature/genui-prod-upgrade-docsync 任务卡
任务主记录:PR #679 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/679;状态:已完成(合入前闭合);owner:weichen
## 基线与授权
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= e634608d97333354c3ead85d47c9e9e368a992a0
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-genui-prod-upgrade-docsync(worktree);分支:feature/genui-prod-upgrade-docsync
- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=6f68e1ece9fe3a765dde7e98955e816bcb1e6c8f776739169191da3353604790;triage_receipt=/tmp/genui-prod-upgrade-doc/triage.json;receipt_sha256=52f7deacbc7f523892a5c6bdae42a3add141712558df706c9d93134ed7d32516
- 授权与交付终点:用户 2026-10-21 选择「升级生产并交给 svc 完整管理」。终点:prod 升级运行 + Documents #119 与本 PR 合入、任务收口。
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
- 人工决策点:仅限目标/范围/验收变更、重大取舍、高后果操作或无法自动判定的业务验收
- 当前阶段:合入;当前增量:I1(升级运维 + 文档同步)
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
## 目标与非目标
- 目标:本机 GenUI `prod`(127.0.0.1:8787,`pg1/prod`)由 `dev@b4649d7`(schema v25,无 identity 能力,svc 不能接管)升级至 `dev@5360884`(v51)并由主检出 `scripts/svc.sh` 完整管理;收口稿部署条目同步。
- 仓外运维(不入仓,本卡记事实):备份 `~/.local/share/agent-system-backups/genui-prod-upgrade-20261021T123748Z/`(prod.dump、prod-final.dump、prod-runtime.tar、保留目录整包 tar.gz);演练实例 `prodrh`(库副本)完整走 adopt → migrate → deploy 并验证后清除;正式:停服 12:41:39 → 迁入 `runtime/prod` 并 bulk-set 重锚 → `adopt --confirm svc-prod-f29fb8e57420` → 候选二进制 `agent-system migrate` → `svc.sh prod deploy --from dev@5360884` → 12:42:39 UI 就绪。新增 `superadmin`(凭据 `runtime/prod/super-admin.env` 0600,经 `AGENT_SYSTEM_ENV_FILE` 传入);原 `admin` 保留。保留目录打包后删除。
- 非目标:GenUI 功能验收清单重跑(NOT_RUN);svc.sh 改动。
## Agent 实施收据
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|---|
| documents-genui-prod-upgrade-docsync | Documents 收口稿单条目 | AGENT-EXECUTION.md | implementation | cursor→antigravity→pi-glm→pi→codex | 1 | cursor / auto(fresh process) | success | applied → Documents 518e381(#119) | 无 | /tmp/genui-prod-upgrade-doc/docs-run.json |
| dev-genui-prod-upgrade-docsync-repin | modules/documents gitlink | AGENT-EXECUTION.md | implementation | cursor→antigravity→pi-glm→pi→codex | 1 | cursor / auto(fresh process) | success | applied → 137a18c | 无 | /tmp/genui-prod-upgrade-doc/dev-run.json |
## 关联方向与规划
- 适用状态:待核对(无适用规划时说明原因)
- 主记录路径/版本:待核对(路径按仓库根目录定位,归档后不变;只引用唯一正文)
- 对应里程碑:待核对
## 需求分析
| ID | 描述 | 项目符合度及依据 | 优先级 | 验收条件 | 依赖 | 结论 |
|---|---|---|---|---|---|---|
| R1 | 待填写 | 待评估 | 待定 | 待定义 | 待确认 | 待评审 |
## 方案与计划
- 方案引用:待填写(含对象版本)
- 实施计划引用:待填写(含增量与需求 ID)
- 测试计划引用:待填写(含需求 ID 与验收条件)
## 阶段记录
| 阶段 | 对象版本 | 状态 | 决策/执行者 | 时间 | 依据 |
|---|---|---|---|---|---|
| 需求拆解 | 待定 | 待执行 | — | — | — |
| 需求评估 | 待定 | 待执行 | — | — | — |
| 需求表确认 | 待定 | 待执行 | — | — | — |
| 方案设计 | 待定 | 待执行 | — | — | — |
| 实施计划 | 待定 | 待执行 | — | — | — |
| 实施与自检 | 待定 | 待执行 | — | — | — |
| 正式测试与验收 | 待定 | 待执行 | — | — | — |
| 代码 Review | 待定 | 待执行 | — | — | — |
| 合入 | 待定 | 待执行 | — | — | — |
| 合入核验与收口 | 待定 | 待执行 | — | — | — |
## 探索试验与迭代
| 增量/试验 | 假设 | 范围 | 判断和停止条件 | 授权/决策依据 | 结果 | 变更影响 | 需重审补测 |
|---|---|---|---|---|---|---|---|
| 待定义 | — | — | — | 待核对 | NOT_RUN | 待评估 | 待评估 |
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|---|---|---|---|
| 启动 | e634608d97333354c3ead85d47c9e9e368a992a0 | e634608d97333354c3ead85d47c9e9e368a992a0 | 2026-10-21T12:44:13Z |
## 证据(Evidence)
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|---|---|---|---|---|---|
## 交付调度
- 调度状态:未入队;调度记录:无
- entry_id:无;attempt_id:无;窗口执行者:无
- 固定候选:无(base/head、组件与相关配置引用实际尝试)
- 调度依据:policy/delivery.md#合入调度
- 固定后证据:源码外 RECEIPT;实际结果后按授权原样导入和闭合
## 结果与验证
- 结果:prod 运行 `dev@5360884e`(svc 管理,desired=running,identity established,schema v51);Documents #119 合入(merge 2e9c3bc);`modules/documents` fcea466→2e9c3bc。
- 验证:Documents #119 head 518e381 CI governance=success(GATE);Dev 以本 PR 当前 HEAD 的 CI 为准。仓外运维 SELF:healthz/readyz/UI=200;superadmin 登录 200、isSuperAdmin=true;未登录 /api/me/profile=401;argv 无 DSN;users(admin、superadmin)与 3 个会话保留。GenUI 功能验收清单 NOT_RUN。
- 影响与剩余项:停机约 1 分钟。svc 不持久化超级管理员凭据:`scripts/svc.sh prod start/restart`(含 recover/watchdog)须带 `AGENT_SYSTEM_ENV_FILE=runtime/prod/super-admin.env`,否则服务拒绝启动(与 dev 同)。发现(非阻塞,未修):v27 前旧库经 svc 部署时身份门禁 ensure 报 identity table missing,须先手工 `agent-system migrate`;`svc.sh adopt` 在旧制品下实际保持 pending 却提示 established。
## 规划进展与下一步
- 本次推进/剩余:待核对(依据实际成果更新适用方案/规划)
- 首选下一任务:待核对(无适用规划、已完成或受阻时如实说明,不虚构任务)
- 推荐依据:待填写
- 前置条件/待确认:待核对
- 预期产物/验收条件:待定义
- 重复任务核对:待核对(已有/在途任务、PR 与规划记录)
- 启动授权:未授权(建议不自动启动新任务,仍须明确授权)
## 合入与清理
- Review 记录:NOT_RUN(审阅智能体/平台身份、base/head、结论、问题闭合及重放依据)
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-21T12:43:34.416643+00:00",
"command": "make precheck",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev",
"before_head": "e634608d97333354c3ead85d47c9e9e368a992a0",
"after_head": "e634608d97333354c3ead85d47c9e9e368a992a0",
"branch": "main",
"target_branch": "main",
"target_head": "e634608d97333354c3ead85d47c9e9e368a992a0",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
"[repo] branch=main head=e634608d97",
"[remote] origin/main=e634608d97 ahead=0 behind=0",
"[tree] clean submodules_drift=0 uninit=0",
"[worktrees] linked=3",
"[initiatives] local cards (not authoritative): none",
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
"[facts] policy/facts.md 6 条事实均在有效期内",
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
"[R0] e634608d97(完整:e634608d97333354c3ead85d47c9e9e368a992a0)",
"[worktree] before-sync: recursive component checks passed",
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
],
"output_sha256": "780774c4e44bd6f550b9d22ee2db80a7ae56d999c3f130cec70d8845b21aff16",
"receipt_sha256": "da371db8c87cef44543025eedbc4f3112b08d6fd0478b4072a1590b844eb9618",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/da371db8c87cef44543025eedbc4f3112b08d6fd0478b4072a1590b844eb9618.json"
}
```
## 闭合记录(合入前折叠)
- 本卡在 PR #679(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/679)合入前闭合并归档,随该 PR 一同合入。
- 该 PR 的合并提交即本任务的落地记录。
- 合入后无需回填,也无需单独的闭合 PR。
@@ -62,8 +62,8 @@ restart 即丢库(回环地址下静默回退 SQLite)。实现:数据库
| 实施与自检 | 任务分支 HEAD | 通过(作者自检,SELF) | Agent | 2026-10-11 | 证据表:bash -n / test-governance / make test / make build |
| 正式测试与验收 | 任务分支 HEAD | 通过(SELF;CI 于 PR HEAD 复跑升 GATE) | Agent | 2026-10-11 | 证据表:隔离真实 PG 验证 27/27 |
| 代码 Review | 任务分支 HEAD | 待 PR/CI | — | — | PR diff + CI(GATE);按既定自主闭环惯例推进 |
| 合入 | 待定 | 未评审 | — | — | — |
| 合入核验与收口 | 待定 | 未评审 | — | — | — |
| 合入 | 1b5e372(PR head) | 通过 | CI(GATE)+自主闭环惯例 | 2026-10-11 | PR #342 CI test/test-pg 双 success;merge commit 452f00e(含两次 task-sync 应对并行合并竞态) |
| 合入核验与收口 | origin/main@452f00e | 通过 | Agent | 2026-10-11 | main 已含 resolve_db_config/tests/docs(fetch 实核);回收与归档见下 |
## 探索试验与迭代
@@ -123,8 +123,8 @@ restart 即丢库(回环地址下静默回退 SQLite)。实现:数据库
## 合入与清理
- 合入记录:NOT_RUN(授权依据、PR、合入 SHA、验证来源)
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
- 合入记录:PR #342 → merge commit 452f00e6d941ccc72fa72a38e008f7b9c800555d(2026-10-11);验证来源 = CI(GATE) 于 PR head 1b5e372 双作业 success + 本卡 SELF 证据五段(bash -n / test-governance / make test / make build / 隔离真实 PG 27/27)
- 清理记录:本会话构建产物(bin/、node_modules、dist、__pycache__ 等 ignored 文件)显式删除后 task-finish 回收 worktree;分支 feature/svc-db-config-recover 尾部一笔仅卡片记录的提交(ec0dd4e)因并行归档(PR #347)不再并入(其内容并入本归档),PR #346 已关闭(2026-10-11,作者)
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
@@ -0,0 +1,133 @@
# fix/genui-prod-upgrade-followups 任务卡
任务主记录:PR #681 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/681;状态:已完成(合入前闭合);owner:weichen
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= ccb6dfaa9cdf7e69eb15d7df024dee55f37b8df1
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-fix-genui-prod-upgrade-followups(worktree);分支:fix/genui-prod-upgrade-followups
- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=88169f094e22b9e6549ea7e86f2f211beb1eba9acb9db9ff9cc51a3af5f13c60;triage_receipt=/tmp/genui-followups-20261021/triage.json;receipt_sha256=659253527394185c956e8005f246d7599118d2d5a6c9f15d6093c4bf3b70b148
- 授权与交付终点:用户 2026-10-21「把剩下的 3件事做了」(GenUI prod 升级遗留:GenUI 功能验收 NOT_RUN、`svc.sh adopt` 误报 established、部署门禁处理不了 v27 前旧库);终点为 svc.sh 修复与 Documents 验收记录经 PR 合入并回收。未授权:prod 业务角色配置(`admin` 建项目权限)、自动迁移、高后果 git 操作
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
## 目标与非目标
- 目标 1(prod 运维,仓外):在 `dev@5360884` 的 prod 上按 2026-10-12 清单重放 GenUI API 验收。结果 PASS(11 项),不调用模型、无费用。`superadmin` 无 `project.create`(新版本该权限只来自角色),因此用 `superadmin` 建临时角色 `genui-accept`(仅 `project.create`)与同名临时账户执行;首登改密后跑完,账户与角色均已停用,`superadmin` 的 GenUI 启用恢复为关闭;prod 日志无 panic。工作文件 `/tmp/genui-prod-accept-20261021/`(accept.sh、accept.txt,不含凭据)。
- 目标 2(`scripts/svc.sh`):`adopt` 读回真实 `db_marker`,未建立时警告而非宣称 established;部署身份门禁遇 `identity table missing` 时提示 pre-v27 库的备份→stop→`migrate --data`→再部署指引(DSN 只经 `AGENT_SYSTEM_DSN`),仍在停旧服务前拒绝、不自动迁移。`tests/test_svc_identity.py` 新增两条回归。
- 目标 3(Documents):收口文档 `部署与运行验收` 要点把 NOT_RUN 换成 10-21 验收结果与角色发现(Documents #120),Dev 跟进 gitlink。
- 非目标:为 prod 建业务角色/给 `admin` 分配建项目权限(业务配置,需用户决定);svc 自动迁移;svc 持久化超级管理员凭据。
## Agent 实施收据
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|---|
| dev-svc-identity-adopt-gate-messages | scripts/svc.sh、tests/test_svc_identity.py | AGENT-EXECUTION.md | implementation | cursor→agy→pi | 1(fresh_session) | cursor | success | refs/agent-exec/…,apply 快进 | 未用 | /tmp/genui-followups-20261021/dev-run.json |
| documents-genui-prod-acceptance-20261021 | Documents docs/proposals/2026-10-12-generative-ui-usage-and-evaluation.md | AGENT-EXECUTION.md | implementation | cursor→agy→pi | 1(fresh_session) | cursor | success | refs/agent-exec/d70e53e1…/bbe777052627,apply 快进 | 未用 | /tmp/genui-followups-20261021/docs-run.json |
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|---|---|---|---|
| 启动 | ccb6dfaa9cdf7e69eb15d7df024dee55f37b8df1 | ccb6dfaa9cdf7e69eb15d7df024dee55f37b8df1 | 2026-10-21T13:32:20Z |
## 证据(Evidence)
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|---|---|---|---|---|---|
## 结果与验证
- 结果:三项完成。prod GenUI API 验收 PASS(仓外运维,见目标 1);`svc.sh` adopt/部署门禁提示修复并加回归;Documents #120(合入 75e43e1)记录验收结果与角色发现,本分支跟进 gitlink。
- 验证:prod 验收 PASS(SELF,`/tmp/genui-prod-accept-20261021/accept.txt` VERDICT PASS,11/11);`python3 -m unittest tests.test_svc_identity` 31 项 OK(1 跳过为真实 PG 专项,SELF,见证据表);`bash -n scripts/svc.sh` 通过;Documents #120 CI success(GATE,f5417fb);本 PR 当前 HEAD 的 CI 为合入依据。
- 影响与剩余项:svc.sh 只改 adopt 与门禁失败提示,成功路径不变。剩余(非目标,需用户决定):prod 无业务角色,`admin` 不能新建项目;svc 不持久化超级管理员凭据。观察:`POST /api/surfaces` 对空 `conversationId` 也返回 201(验收首轮误用触发,非阻塞,未处理)。
## 合入与清理
- Review 记录:当前主模型(host,非实施会话)审阅 base ccb6dfa..0e6180c 实际 diff:svc.sh 35 行、测试 45 行、gitlink 1 行、Documents 单要点;结论通过,无阻塞项;重放依据为上方测试证据与 bash -n。
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-21T13:31:41.603289+00:00",
"command": "make precheck",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev",
"before_head": "ccb6dfaa9cdf7e69eb15d7df024dee55f37b8df1",
"after_head": "ccb6dfaa9cdf7e69eb15d7df024dee55f37b8df1",
"branch": "main",
"target_branch": "main",
"target_head": "ccb6dfaa9cdf7e69eb15d7df024dee55f37b8df1",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
"[repo] branch=main head=ccb6dfaa9c",
"[remote] origin/main=ccb6dfaa9c ahead=0 behind=0",
"[tree] clean submodules_drift=0 uninit=0",
"[worktrees] linked=4",
"[initiatives] local cards (not authoritative): none",
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
"[facts] policy/facts.md 6 条事实均在有效期内",
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
"[R0] ccb6dfaa9c(完整:ccb6dfaa9cdf7e69eb15d7df024dee55f37b8df1)",
"[worktree] before-sync: recursive component checks passed",
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
],
"output_sha256": "09b9e9691664020d0e7e0d54d516f261c432989878fe18cd76fdf9d5483b4a69",
"receipt_sha256": "9aaf14e57c338c29c559dfe23ca5e66fa8c9d2d776803f3bcbc65f474cfd294b",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/9aaf14e57c338c29c559dfe23ca5e66fa8c9d2d776803f3bcbc65f474cfd294b.json"
}
```
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-21T13:38:36.782459+00:00",
"command": "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_svc_identity",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-fix-genui-prod-upgrade-followups",
"before_head": "0e6180cbd53b68da9b5810c28506225d6d59085e",
"after_head": "0e6180cbd53b68da9b5810c28506225d6d59085e",
"branch": "fix/genui-prod-upgrade-followups",
"target_branch": "main",
"target_head": "a736d5a420370cff052bd946f6d6397d4c354547",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"..................s............",
"----------------------------------------------------------------------",
"Ran 31 tests in 36.292s",
"",
"OK (skipped=1)"
],
"output_sha256": "5db0f58ef46fff16da8a9e48cbf54b5efe5cb93514ecb06def22ba430d7af68e",
"receipt_sha256": "c4f367c2b1590a823f0e3cfb6868126788932ec74fcae2c13d5b93cc9b243ce3",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/c4f367c2b1590a823f0e3cfb6868126788932ec74fcae2c13d5b93cc9b243ce3.json"
}
```
## 闭合记录(合入前折叠)
- 本卡在 PR #681(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/681)合入前闭合并归档,随该 PR 一同合入。
- 该 PR 的合并提交即本任务的落地记录。
- 合入后无需回填,也无需单独的闭合 PR。
| 同步(记录前对象) | a736d5a420370cff052bd946f6d6397d4c354547 | 62ba1a225fcf1938ee97af2e08e1dbeca142fe97 | 2026-10-21T13:40:41Z |
@@ -0,0 +1,95 @@
# fix/multi-instance-test-failure-diagnostics 任务卡
任务主记录:PR #678 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/678;状态:已完成(合入前闭合);owner:weichen
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 5360884e9283d962caf945d944df05b5e5059177
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-fix-multi-instance-test-failure-diagnostics(worktree);分支:fix/multi-instance-test-failure-diagnostics
- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=e2453ebdabc4cc9c61bb25a3535fbe7cf0d0fe687f2ea7bc565b799dcf0fcb6d;triage_receipt=/tmp/triage-mi-test-hygiene-20261021.json;receipt_sha256=4f0a610213a5f6363e303e7129cd09a630ae1a04acbaa5116635c804b2f7f56c
- 授权与交付终点:修复 `tests/integration/multi-instance` 两处测试卫生缺陷(轮询静默超时、断言早于恢复视图),让失败直指真正原因。不得改 Backend 或任何生产代码(其根因已由 Backend PR #237 修复)、不得弱化断言、不得放大超时预算。
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
## 目标与非目标
<预期结果、受影响入口/模块/契约;明确不做什么。>
## Agent 实施收据
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|---|
| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — |
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|---|---|---|---|
| 启动 | 5360884e9283d962caf945d944df05b5e5059177 | 5360884e9283d962caf945d944df05b5e5059177 | 2026-10-21T12:07:32Z |
## 证据(Evidence)
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|---|---|---|---|---|---|
## 结果与验证
- 结果:owner_crash_takeover 的 interrupted 轮询改为有界等待 + 显式失败(点名未观察到的 run 状态序列、轮询实例、实际耗时与预算、实例日志),未收口时不再落到后续 POST 的误导性 409;restart_invariants 在断言消息数之前新增「恢复视图」有界等待(存在 interrupted run 且无 pending/running/waiting_hitl),未恢复时给出点名判据的失败信息;其余 `t.Fatal`→`t.Fatalf` 仅补失败信息,断言条件未变。新增 `runStatuses` 与 `e.instanceOf` 两个小helper 供等待条件与失败信息复用。
- 验证:PASS——真实 PostgreSQL(127.0.0.1:54331,fsync=on)下空闲机器 15.1s 通过、96 忙循环压满 32 核 17.4s 通过(新增失败路径未误触发);`gofmt`/`go vet` 干净;独立 Review(pi-glm,PASS 无阻塞项)报告 `.task/reviews/2026-10-21-mi-diagnostics-review.md`,6 项非阻塞发现中第 1 项如实指出恢复视图判据的 interrupted 半边由历史 run 平凡满足(不检出 B 重启对账回归,但改动前同样不检出,覆盖无回退)。
- 影响与剩余项:不改生产代码、不弱化任何断言、未放大任何超时预算、包级 `go test -timeout` 未动。Review 其余非阻塞项(busy 白名单对新增非终态 status 不设防、轮询只附 logA、`instanceOf` 守卫不可达、顺序耦合、瞬态 5xx 即失败)均为既有共享弱点或死代码,不阻塞。
## 合入与清理
- Review 记录:PASS(独立会话 pi-glm / zai-coding-cn/glm-5.3-flash;base 5360884e9283d962caf945d944df05b5e5059177,head 232477b22f997bc4ab87b5983ae0b350f6ae4c43;报告 `.task/reviews/2026-10-21-mi-diagnostics-review.md`;无阻塞项,6 项非阻塞发现;重放依据:host 用真实 PG 在空闲与 96 负载两种条件下各跑一次多实例验收,均通过)
- 冲突记录:NOT_RUN(一次交付前同步,0 提交 behind)
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-21T12:06:44.036919+00:00",
"command": "make precheck",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev",
"before_head": "5360884e9283d962caf945d944df05b5e5059177",
"after_head": "5360884e9283d962caf945d944df05b5e5059177",
"branch": "main",
"target_branch": "main",
"target_head": "5360884e9283d962caf945d944df05b5e5059177",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"[repo] branch=main head=5360884e92",
"[remote] origin/main=5360884e92 ahead=0 behind=0",
"[tree] clean submodules_drift=0 uninit=0",
"[worktrees] linked=3",
"[initiatives] local cards (not authoritative): none",
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
"[facts] policy/facts.md 6 条事实均在有效期内",
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
"[R0] 5360884e92(完整:5360884e9283d962caf945d944df05b5e5059177)",
"[worktree] before-sync: recursive component checks passed",
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch"
],
"output_sha256": "6a290a280e9e8ec5b78b7bf430d194dbe3a55fbbd9507b05b6c12899b01ceebb",
"receipt_sha256": "577c22f0e49c012c394aeb6dbcc188fbf681ab8315c5864da6da3d0ca62b90f8",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/577c22f0e49c012c394aeb6dbcc188fbf681ab8315c5864da6da3d0ca62b90f8.json"
}
```
## 闭合记录(合入前折叠)
- 本卡在 PR #678(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/678)合入前闭合并归档,随该 PR 一同合入。
- 该 PR 的合并提交即本任务的落地记录。
- 合入后无需回填,也无需单独的闭合 PR。
@@ -0,0 +1,208 @@
# fix/pal-component-delivery-pins 任务卡
任务主记录:PR #682 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/682(merge f23f5bb01a);状态:已完成(回填);owner:weichen
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= a496ea0f0249b2b412354677ef97612ccd692d25
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-fix-pal-component-delivery-pins(worktree);分支:fix/pal-component-delivery-pins
- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=5bd167003a0df76cc4ce5252be7b24ce28dbcc9614493a978e3e1b7a8e864dcc;triage_receipt=/tmp/pal-pins-triage.json;receipt_sha256=0f53fa4aab99a9d10ebac3f76c9be1308b4b84b1066a59230f1497bff05e917c
- 授权与交付终点:<本次允许的动作与终点;未授权事项写明>
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
## 目标与非目标
- 预期结果:把 PR #634 已合入 Dev main、但未进入交付件的组件内容补齐——Backend 与 RDM 各自合入 main,Dev 推进对应 pin,使 U1–U5 真正进入可部署制品。
- 已核实基线(2026-10-21 建任务时点):Dev main 钉 `modules/backend=dfb31da`(不含生命周期内容)、`modules/frontend=113103f`(即 U6 交付头,随 Dev 交付但未入 Frontend main)、RDM pin `c6304224`(2026-10-13,早于角色权威切到 Backend 的 #454)。上述取值均为时点快照,精确落后量不作依据。
- 非目标:不改 U1–U5 的规格语义;不部署(C16 另起,需 pin 就位后进行);不处理 `feature/rdm-two-trees-followup`(PR #635)的范围。
## 决策记录
- **D1(role cap 是否受 R3 carve-out 约束)——用户裁决 2026-10-21,选 A1。** RDM 的 `evalPerm` 有两层不同的覆盖:role cap(`switch` 的 `RoleReasonCapDenied` 与 legacy 的 `!defined || !capAllowed`)与项目覆盖层(末尾 `ps.OverrideFor`)。计划 R3 原文只约束后者(原文用词为 per-action member/project **overrides**)。
- 裁决内容:**只让项目覆盖层不再撤销创建者权威本身授予的权限**;role cap 维持现状、继续阻断创建者。覆盖层的「授予」方向也不变。平台上限、账号停用、职位停用、Linked.Conflicts、归档/结项状态守卫、对非创建者的拒绝全部不变。
- 实现:`internal/rdmapp/perms_api.go` 的 `evalPerm` 末尾,`creatorGranted := base && relation == perms.RoleOwner && p.CreatedBy == actor && isManagementAction(act)`;`if set && (override || !creatorGranted)`。
- 该裁决曾以「Decision A1」写进 `plugins/rdm/system_projects_test.go` 的注释,但当时未在任何仓内决策记录中留痕——这是本卡补记的原因。
- **D2(archive/restore 授权门不得被 R3 收敛吞掉)——由计划原文直接决定,非新决策。** U2 第 219 行:「Do not change archive/restore, which remains a separate lifecycle.」故 `canManageProject` 按 act 分流:`ActProjectArchive` 沿用 main 的「创建者或显式授权」门,其余走 R3 的 `requireProjectManagement`。
- **D3(ArchivedAt 守卫必须还原,ClosedAt 不设)——由计划原文与 R6 共同决定。** 归档属 Out of scope,main 的只读守卫必须恢复(含创建者);结项属 U4/R6,R6 明文「The creator can still use project-management actions needed to reopen or administer the project.」,故 ClosedAt 刻意不设守卫。
- **D4(平台上限对创建者同样生效)——由领域规则与 SR-G9 决定。** 「system.grants 与平台上限写入门禁保持 system.permissions.manage,不得因 R13 放宽」;`perms.SetCap` 注释「所有业务主体均受约束」;SR-G9 创建者「仍受有效角色与平台 cap」。故 `requireProjectManagement` 带 act 参数并恢复 `CapAllows`。
## Agent 实施收据
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|---|
| dev-advance-rdm-and-dm-pins | `tests/integration/lifecycle/rdm.pin` + `deps/development-manager` 指针 | AGENT-EXECUTION digest ccceb3bf | implementation | cursor→agy→pi-glm→pi→codex | 1 | cursor(session not-observable) | success | b5a0abf(refs/agent-exec…),已 apply 快进入分支 | never | /tmp/dev-pin-run.json |
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|---|---|---|---|
| 启动 | a496ea0f0249b2b412354677ef97612ccd692d25 | a496ea0f0249b2b412354677ef97612ccd692d25 | 2026-10-21T03:47:24Z |
## 证据(Evidence)
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|---|---|---|---|---|---|
## 组件交付结果(2026-10-21)
| 组件 | 状态 | 依据 |
|---|---|---|
| Backend | ✅ 已进 main(`7158bea`) | 生命周期工作已由 PR #234 合入;Dev 的 backend pin `f084d10` 已含该提交(他人推进,已核实祖先关系) |
| RDM | ✅ 已进 main(`bc8db88`) | PR #460,五轮独立 Review 终判 PASS,CI 双作业绿 |
| Frontend | ✅ 随 Dev 交付 | pin `113103f` 即 U6 交付头 |
### 交付前的状态
**可核事实**:Dev 的门禁从未消费交付分支。`Makefile` 的 `test` 目标只测**各组件钉住的检出**(`test-backend` 测 `modules/backend` 的 pin),`rdm.pin` 仅被 `test-lifecycle` / `test-regulations-kb` 消费,而真实生命周期门禁需要真实 PostgreSQL 与 RDM 源仓。因此「Dev 门禁全绿」与「交付分支本身有缺陷」可以同时成立。
**SELF 观测(非本仓可复核,2026-10-21 时点)**:我在组件仓的临时 worktree 上检出交付分支顶端提交并运行其自带测试,观察到失败——
- Backend `bd640df`:单跑 `TestConversationWorkErrorCategorization` 失败,同一用例在 `origin/main` 通过。
- RDM `301cf56`:全量 `go test ./...` 有 4 个用例失败,同一运行中 `internal/rdmapp` 绿、`plugins/rdm` 红。
这两个观测随后被下面的六项缺陷所印证与闭合。若需复核,请在对应组件仓按上述提交检出并运行其自带测试;本仓无法复现,故**不得把本段当作 GATE 证据引用**。
**勘误**:提交 `537bf66` 的信息中写有「red-at-tip finding」,该表述把上述 SELF 观测说成了既定发现;本条以本节为准。`537bf66` 另写的「差 27 文件/1379 行」「落后 RDM main 918 提交」为当时点的粗略量测,与任何自然口径都不精确对应,现予作废,不作为依据。
### 修复过程中发现并闭合的授权缺陷
| 编号 | 内容 | 性质 |
|---|---|---|
| B1 | `canManageProject` 被整体替换,archive/restore 授权门被吞;两个 main 测试被反向改写成「获授者也拒绝」并注释「新旧一致」 | 越界 + 伪装成延续 |
| B2 | 已归档项目只读守卫丢失,改名/成员增删/override 全放行 | 越界(计划禁止改 archive) |
| B3 | **平台上限失效**:管理员 `SetCap` 关闭后创建者管理写入全部成功,而 member add 被拒、remove 放行 | **安全天花板被绕过** |
| F1 | 已结项项目上命令面 member-add 拒绝创建者,直接 API 却放行 | 自相矛盾(违反 R6) |
| F2 | role cap 只挡 add 不挡 remove/suspend/reactivate | 口径不一致 |
| F3 | render-profile 族允许非创建者凭 owner 标签或显式覆盖行使 `project.settings` | 违反 R3 |
结构根因:R3 把管理路径从 `evalPerm` 迁到 `requireProjectManagement` 时**逐项丢门**(先丢归档,再丢平台上限)。最终形态是两个独立维度——`managementLifecycleDenial`(归档拒绝、结项按 R6 不设)与 `managementCeilingDenial`(平台上限、role cap、联动冲突、停用、有效访问)。
### 顺带落地的治理修复
Development-Manager PR #32(`2c2e372`)为 `admit` 增加「权威基线内容认可」:暂存路径若与权威基线**逐字节相同**即免 transport。已实测生效——原本 65 个暂存路径全被拒,修复后仅 5 个需 transport,其余 60 个自动认可;最终以空合并提交记录祖先关系时 `admit: ok`,全程无 transport 妥协。
## 结果与验证
- 结果:RDM pin 与 dm pin 均已推进并推送(PR #682);**CI `test-pg` 红,当前 blocked。**
### test-pg 失败(唯一阻塞)
`svc-realcombo` success、`test` 通过,`test-pg` 在「Clone Agent-System-RDM with nested pins」步骤失败:组合二进制编译报 `missing go.sum entry for module providing package agent-document-renderer/{render,markdown,model,profile,profiles,word}`。
已核实的成因:RDM `bc8db88` 的 `go.mod` 用本地路径 replace `agent-document-renderer => pins/agent-document-renderer`,因此其 `go.sum` 中**没有任何** agent-document-renderer 条目(实测计数 0)。Dev 侧构建会把 RDM 的 backend replace 重写到 `../backend`(即 Dev 的 `modules/backend`,当前 `f084d10`),而该 backend 直接 import document-renderer 的上述包,于是 RDM 的模块图无法提供对应条目。另注:RDM 自身钉的 backend 是 `bd640df`,与 Dev 的 `f084d10` 已分叉。
这正是「只有真实组合门禁才能发现」的那一类不兼容——`make test` 全绿(exit 0)也照样漏掉。
### 根因(已定位,非制品缺陷)
同一份 RDM 在本地跑 `scripts/build-product.sh` **成功**(产物 `9ea3fcc8cef0df8a`),说明 RDM 制品无缺陷。报错只出现在 CI,原因是 **RDM 的 pins 布局从 4 个变成 6 个**(新增 `pins/agent-document-renderer`、`pins/agent-system-kb-regulations`):
| 提交 | pins 数量 |
|---|---|
| `legacy_sha` 72bb1fec | 4 |
| 旧 `prev_sha` 3484ec2 | 4 |
| 旧 `sha` c6304224 | 4 |
| 新 `sha` bc8db88 | **6** |
| bc8db88 的第一父 59b3624 | 6 |
CI 步骤逐个检出并构建三个 pin,依赖三者布局一致。新 `sha` 是 6-pin,而 `prev_sha`/`legacy_sha` 仍是 4-pin,布局不再一致;本次 fail-loud 断言直接点名:`rdm pin c6304224 的 pins/agent-system-frontend 未落地:缺少 go.mod`。
我先前把 `prev_sha` 设成旧 `sha`(c6304224)是错的——它是 4-pin 布局,且我在单元包与注释里都未核对布局一致性,这是本次返工的直接原因。
### 进展(run 4714 / job 7830)
CI 物化修复**已生效**:三个 pin 全部通过 fail-loud 断言并各自构建成功,`missing go.sum entry` 不再出现。`svc-realcombo` 与 `test` 均绿。
失败因此推进到下一道门禁——真实组合门禁 `make test-lifecycle`,三个用例红:
| 用例 | 失败点 |
|---|---|
| `test_prev_and_current_pin_switch_and_back` | `svc.sh` 的 `git fetch(独立 RDM 源仓:rdm)` 退出 1 |
| `test_mixed_batch_updates_forward_and_deploy_reverses` | 同上,更新检测失败 |
| `test_phase_interrupt_reconciles_on_real_artifacts` | 同上,退出码 86 |
即:CI 里 `rdm/` 是 `--depth=1` 克隆 + 对三个 pin 的 `--depth=200` 取,`svc.sh` 物化 RDM 提交时要对该源仓做 `git fetch`,在浅克隆/对象边界下失败。这属于 `scripts/svc.sh` 的 RDM 源仓取用方式与 CI 浅克隆之间的新问题,**不在本 PR 的 pin 推进范围内**,且我未继续深挖。
### 我的两处失误(均已修正)
1. 选 `prev_sha` 时未核对 pins 布局一致性,把 4-pin 的旧 sha 当成 prev(已改为 6-pin 区间内最老的提交)。
2. CI 断言写成「每个 pin 必须有 go.mod」——pin 集合混有 Go 仓与非 Go 仓(前端仓根无 go.mod),产生假失败(已改为「HEAD 等于 gitlink 且工作树有已跟踪文件」)。
### 未决(需决策)
- `prev_sha` 应取 bc8db88 的第一父 `59b3624`(6-pin、与新 sha 布局一致、紧邻交付);但这样 `prev_sha` 与 `sha` 仅隔一个合并,生命周期套件的「来回部署身份稳定性」覆盖力度下降。
- `legacy_sha` 必须保持「无身份」语义,因而必然是老提交(4-pin)。构建循环是否应继续要求三个 pin 都可构建?若否,则该步骤对 `legacy_sha` 应放宽断言或单独处理——这属于 CI 步骤语义的设计取舍,不宜由实现侧默定。
### 其余验证
- PASS|pin 取值自洽:`sha=bc8db88` 含交付内容(`managementCeilingDenial`、`managementLifecycleDenial`、`txProjectManaged`、`MemberAddResolveFailure` 均在其树内);`prev_sha` 为原 pin,供生命周期套件来回部署;`legacy_sha` 未动。
- PASS|`make test` 在 `98d794e` 上 exit 0、无 FAIL。
- PASS|独立 Review 首轮判 FAIL(任务卡把会话内观测写成既定结论),已按其要求改写为可核表述 + SELF 观测 + 勘误,复审通过项见上文。
- NOT_RUN|精确 HEAD 三作业 GATE(`test-pg` 红)。本地 SELF 不可替代,不得合入。
- 结果:
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
- 影响与剩余项:
## 合入与清理
- Review 记录:NOT_RUN(审阅智能体/平台身份、base/head、结论、问题闭合及重放依据)
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-21T03:46:26.935822+00:00",
"command": "make precheck",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev",
"before_head": "a496ea0f0249b2b412354677ef97612ccd692d25",
"after_head": "a496ea0f0249b2b412354677ef97612ccd692d25",
"branch": "main",
"target_branch": "main",
"target_head": "a496ea0f0249b2b412354677ef97612ccd692d25",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
"[repo] branch=main head=a496ea0f02",
"[remote] origin/main=a496ea0f02 ahead=0 behind=0",
"[tree] clean submodules_drift=0 uninit=0",
"[worktrees] linked=5",
"[cards] 已合入未闭合任务卡:feature-project-access-lifecycle.md——make task-close 回填归档(主检出运行)",
"[initiatives] local cards (not authoritative): none",
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
"[facts] policy/facts.md 6 条事实均在有效期内",
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
"[R0] a496ea0f02(完整:a496ea0f0249b2b412354677ef97612ccd692d25)",
"[worktree] before-sync: recursive component checks passed",
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
],
"output_sha256": "d63eb6dfcf96743449b73edabd63a126308f731b7528b1e249ec5a7f4ce8aae4",
"receipt_sha256": "b659e67e444244c0f70ec7f71c89044666ba229581bab6ccca200bbf491cd24c",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/b659e67e444244c0f70ec7f71c89044666ba229581bab6ccca200bbf491cd24c.json"
}
```
| 同步(记录前对象) | ccb6dfaa9cdf7e69eb15d7df024dee55f37b8df1 | 2af80d6a24bcde0a6da1f091acdb25c7e1e9a1f4 | 2026-10-21T13:32:30Z |
| 同步(记录前对象) | a736d5a420370cff052bd946f6d6397d4c354547 | e8dbbe001ae12048217c0a763ab7699d183f5b1f | 2026-10-21T13:57:35Z |
## 闭合记录(card-autoclose 回填)
- 依据:merge f23f5bb01a8b96ab90686d3a1b07791d7f473de2(PR #682)已包含于 origin/main,分支成果已落盘;本段为合入后回填。
- 合并主题:Merge pull request 'Advance the RDM artifact pin so the delivered lifecycle work actually ships' (#682) from fix/pal-component-delivery-pins into main
- 归档:随闭合移入 .task/archive/;任务执行期的过程证据见原任务记录与 PR。
@@ -0,0 +1,96 @@
# fix/svc-db-card-merge-backfill 任务卡
任务主记录:PR #680 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/680;状态:已完成(合入前闭合);owner:weichen
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= e634608d97333354c3ead85d47c9e9e368a992a0
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-fix-svc-db-card-merge-backfill(worktree);分支:fix/svc-db-card-merge-backfill
- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=3d50b12890499e5825a8b9154efa3f27b7d1557b29cb69fb2267672abe118c88;triage_receipt=/tmp/triage-svcdb-cardbackfill-20261021.json;receipt_sha256=2f6dc84718b854696af1bdedc08e18881a94887e78af9fdd9fdeafd80fb8befb
- 授权与交付终点:把事故修复任务 svc-db-config-recover 已写好但从未落地的合入回填合入 main,只改 `.task/archive/feature-svc-db-config-recover.md` 的 4 行账务字段(阶段表的合入/合入核验两行 + 合入与清理段的合入/清理记录两行)。不得改动该卡其余任何内容、不得改代码、不得改其他卡。
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
## 目标与非目标
<预期结果、受影响入口/模块/契约;明确不做什么。>
## Agent 实施收据
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|---|
| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — |
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|---|---|---|---|
| 启动 | e634608d97333354c3ead85d47c9e9e368a992a0 | e634608d97333354c3ead85d47c9e9e368a992a0 | 2026-10-21T13:01:16Z |
## 证据(Evidence)
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|---|---|---|---|---|---|
## 结果与验证
- 结果:`.task/archive/feature-svc-db-config-recover.md` 的 4 行账务字段由「待定/未评审/NOT_RUN」回填为已发生的合入事实:阶段表 `合入`/`合入核验与收口` 两行、`## 合入与清理` 的 `合入记录`/`清理记录` 两行。内容逐字取自 `feature/svc-db-record-backfill` 分支上早已写好但从未落地的同一段文本,未新增任何断言性表述。分支本身随之失去唯一未并入内容。
- 验证:PR 合入事实今日重新取证——PR #342 `merged=true`、merge commit `452f00e6d941ccc72fa72a38e008f7b9c800555d`,且该 merge commit 是 `origin/main` 的祖先;回填文本另提到的 PR #346 为 closed 未合并、#347 已合并,与文本描述一致。CI 结论(`test`/`test-pg` 双 success)沿用该任务自身记录的取证口径,本次未重新取证:该 SHA 的 run 已超出 Gitea Actions API 最近 60 条窗口(2026-10-11),commit-status 接口对这两个 context 返回空状态。文本中该结论本就归属于 PR #342 的记录,非本次新主张。
- 影响与剩余项:只动一张已归档卡的账务字段;不改代码、不改该卡的需求/实施/验证/Review 历史;`feature/svc-db-record-backfill` 分支的未并入内容清零。
## 合入与清理
- Review 记录:NOT_APPLICABLE(纯账务字段回填:4 行 diff,逐字取自已存在的同段文本;PR 合入事实由 host 以 Gitea API 独立复核,见「结果与验证」。按 policy,`.task/**` 属 host 豁免路径,实施不经外部 Agent;独立 Review 由本 PR 的 CI 与 owner 复核承担)
- 冲突记录:NOT_RUN(一次交付前同步,0 提交 behind;diff 仅一张归档卡的 4 行账务字段)
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-21T13:00:15.566597+00:00",
"command": "make precheck",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev",
"before_head": "e634608d97333354c3ead85d47c9e9e368a992a0",
"after_head": "e634608d97333354c3ead85d47c9e9e368a992a0",
"branch": "main",
"target_branch": "main",
"target_head": "e634608d97333354c3ead85d47c9e9e368a992a0",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"[repo] branch=main head=e634608d97",
"[remote] origin/main=e634608d97 ahead=0 behind=0",
"[tree] clean submodules_drift=0 uninit=0",
"[worktrees] linked=4",
"[initiatives] local cards (not authoritative): none",
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
"[facts] policy/facts.md 6 条事实均在有效期内",
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
"[R0] e634608d97(完整:e634608d97333354c3ead85d47c9e9e368a992a0)",
"[worktree] before-sync: recursive component checks passed",
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch"
],
"output_sha256": "10a3d0e2b4a11180b33c0f27fccccae9f5e1169b677b050628fa038a41d53643",
"receipt_sha256": "8d5673f1fabb006921454476d0ea021e42696ee6b50c5d55008e7d58f1e0340c",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/8d5673f1fabb006921454476d0ea021e42696ee6b50c5d55008e7d58f1e0340c.json"
}
```
## 闭合记录(合入前折叠)
- 本卡在 PR #680(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/680)合入前闭合并归档,随该 PR 一同合入。
- 该 PR 的合并提交即本任务的落地记录。
- 合入后无需回填,也无需单独的闭合 PR。
| 同步(记录前对象) | ccb6dfaa9cdf7e69eb15d7df024dee55f37b8df1 | 588e77d1d8a0f1bd0e15afc4204b1885084ba067 | 2026-10-21T13:22:00Z |
@@ -0,0 +1,165 @@
# fix/svc-superadmin-envfile-surface-conv 任务卡
任务主记录:PR #683 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/683;状态:已完成(合入前闭合);owner:weichen
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= d9e9c0560a2767539f4415b579adbeff86a22ba6
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-fix-svc-superadmin-envfile-surface-conv(worktree);分支:fix/svc-superadmin-envfile-surface-conv
- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=1c2e2ec0089a4a524959716deb24ea3e48312199979f87e70337c4b40197747b;triage_receipt=/tmp/svc-sa-surface-20261021/triage.json;receipt_sha256=2adcb48f6e44ae1996bb1e0b09ba34860e4e0a2ae3e5b3dce4e0a84a19a6375e
- 授权与交付终点:用户 2026-10-21「未处理的其他记录也解决修复一下」(svc 不持久化超级管理员凭据;`POST /api/surfaces` 接受空 `conversationId`);Backend 单元 failed-unknown 后用户选择「全新重跑一次」。终点:Backend 与 Dev 经 PR 合入,并以 `AGENT_SYSTEM_ENV_FILE` 重启 prod 一次登记凭据路径。未授权:prod 业务角色配置、高后果 git 操作
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
## 目标与非目标
- 目标 1(`scripts/svc.sh`):agent 服务新增登记键 `superadmin_envfile=`,只存凭据文件绝对路径。deploy/start/restart 时若环境给出 `AGENT_SYSTEM_ENV_FILE`,先在停旧服务前校验(普通文件、属当前用户、无组/其他权限位),通过后登记;空值清除登记。拉起 serve 时本次环境未显式给超管凭据则转发登记路径,recover/watchdog 的清空环境子进程因此也能拉起认证实例。`status` 显示登记路径;`docs/svc.md` §4.2 补说明;新增 `tests/test_svc_superadmin_envfile.py`。
- 目标 2(Backend):`POST /api/surfaces` 新建页面(无 `surfaceId`)时要求本人存在的 `conversationId`:空值 400,不存在或他人会话 404;修订语义不变。原因:空/未知会话使页面无项目归属,动作接口随之跳过项目生命周期检查。
- 非目标:svc 读取或复制凭据内容;surfacesvc/模型工具路径改动;prod 业务角色配置。
## Agent 实施收据
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|---|
| dev-svc-superadmin-envfile-persist | scripts/svc.sh、tests/test_svc_superadmin_envfile.py、docs/svc.md | AGENT-EXECUTION.md | implementation | 按配置 | 1(fresh_session) | cursor | success | apply 快进 0e6453b | 未用 | /tmp/svc-sa-surface-20261021/dev-run.json |
| backend-surface-submit-conversation-required | Backend internal/server/platform_api.go、surface_api_test.go | Backend AGENT-EXECUTION.md | implementation | 按配置 | 1(fresh_session) | pi | failed-unknown(隔离区执行 `git submodule update --init` 致 local_config 变化,违反单元约束;源 worktree 未变,现场保留)→ fail-stop | 无 | 未用 | /tmp/svc-sa-surface-20261021/backend-run-attempt1-failed-unknown.json |
| backend-surface-submit-conversation-required(用户批准全新重跑,单元补子模块初始化与配置逐字节恢复约束) | 同上 | Backend AGENT-EXECUTION.md | implementation | 按配置 | 1(fresh_session) | pi | success | apply 快进 1e73822→954e744(amend 规范提交,tree 不变) | 未用 | /tmp/svc-sa-surface-20261021/backend-run.json |
| dev-svc-superadmin-surface-backend-repin | modules/backend → 528e3a4(Backend #239) | AGENT-EXECUTION.md | implementation | 按配置 | 1(fresh_session) | cursor | failed-unknown(worker 改动正确但终态记录未从信封解出;源 worktree 未变,隔离现场保留)→ fail-stop | 无 | 未用 | /tmp/svc-sa-surface-20261021/repin-run-attempt1-failed-unknown.json |
| dev-svc-superadmin-surface-backend-repin(用户批准全新重跑) | 同上 | AGENT-EXECUTION.md | implementation | 按配置 | 1(fresh_session) | cursor | success | apply 快进 → 3c0faf76(amend 规范提交,tree 不变) | 未用 | /tmp/svc-sa-surface-20261021/repin-run.json |
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|---|---|---|---|
| 启动 | d9e9c0560a2767539f4415b579adbeff86a22ba6 | d9e9c0560a2767539f4415b579adbeff86a22ba6 | 2026-10-21T14:04:06Z |
## 证据(Evidence)
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|---|---|---|---|---|---|
## 结果与验证
- 结果:两项完成。(1) svc.sh 登记 `superadmin_envfile=`(只存路径),deploy/start/restart 校验后登记,裸 restart/recover 自动转发;(2) Backend #239 合入(`528e3a4`),`POST /api/surfaces` 新建页要求本人会话;本分支 gitlink 已钉到该提交。合入后将以 `AGENT_SYSTEM_ENV_FILE` 重启 prod 一次以登记路径。
- 验证:`tests.test_svc_superadmin_envfile`+`test_svc_recover`+`test_svc_identity` 51 项 OK(1 跳过,SELF,见证据表);`bash -n scripts/svc.sh` 通过;Backend #239 CI success(GATE,954e744);本 PR 当前 HEAD 的 CI 为合入依据。
- 影响与剩余项:svc 成功路径不变,只增路径登记与转发;Backend 只改新建页校验。剩余(非目标):prod 无业务角色时 `admin` 仍不能新建项目。
## 合入与清理
- Review 记录:当前主模型(host,非实施会话)审阅实际 diff:svc.sh/docs/测试、Backend handler+测试(经 #239)、gitlink;结论通过,无阻塞项;重放依据为上方测试证据与 Backend CI。
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-21T14:03:32.182767+00:00",
"command": "make precheck",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev",
"before_head": "d9e9c0560a2767539f4415b579adbeff86a22ba6",
"after_head": "d9e9c0560a2767539f4415b579adbeff86a22ba6",
"branch": "main",
"target_branch": "main",
"target_head": "d9e9c0560a2767539f4415b579adbeff86a22ba6",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
"[repo] branch=main head=d9e9c0560a",
"[remote] origin/main=d9e9c0560a ahead=0 behind=0",
"[tree] clean submodules_drift=0 uninit=0",
"[worktrees] linked=3",
"[initiatives] local cards (not authoritative): none",
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
"[facts] policy/facts.md 6 条事实均在有效期内",
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
"[R0] d9e9c0560a(完整:d9e9c0560a2767539f4415b579adbeff86a22ba6)",
"[worktree] before-sync: recursive component checks passed",
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
],
"output_sha256": "d56c09db1d3fa308d17976569429fb7ab950587f2a97e9438432c21db6de7f25",
"receipt_sha256": "ec446f0a238e6f6c64167ff09c23059b47bf08df92852d1ac12ee540348affed",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/ec446f0a238e6f6c64167ff09c23059b47bf08df92852d1ac12ee540348affed.json"
}
```
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-21T14:22:12.910602+00:00",
"command": "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_svc_superadmin_envfile tests.test_svc_recover tests.test_svc_identity",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-fix-svc-superadmin-envfile-surface-conv",
"before_head": "0e6453bd530feedd973ccfb05f587c5d9d9a6ffc",
"after_head": "0e6453bd530feedd973ccfb05f587c5d9d9a6ffc",
"branch": "fix/svc-superadmin-envfile-surface-conv",
"target_branch": "main",
"target_head": "d9e9c0560a2767539f4415b579adbeff86a22ba6",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"......................................s............",
"----------------------------------------------------------------------",
"Ran 51 tests in 52.867s",
"",
"OK (skipped=1)"
],
"output_sha256": "5cde79b38a542c3831ae872cd4d7a2b9cd0ba84b0b129442ded6d4adea577a27",
"receipt_sha256": "3d71bc9d4f8e5ed81db87aae76cc3e649dd90f09ee17dafc1bb8ab494725a5ce",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/3d71bc9d4f8e5ed81db87aae76cc3e649dd90f09ee17dafc1bb8ab494725a5ce.json"
}
```
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-21T15:12:21.352177+00:00",
"command": "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_svc_superadmin_envfile tests.test_svc_recover tests.test_svc_identity",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-fix-svc-superadmin-envfile-surface-conv",
"before_head": "3c0faf76b06b999855733bc2a452476793c5d2a0",
"after_head": "3c0faf76b06b999855733bc2a452476793c5d2a0",
"branch": "fix/svc-superadmin-envfile-surface-conv",
"target_branch": "main",
"target_head": "d9e9c0560a2767539f4415b579adbeff86a22ba6",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"......................................s............",
"----------------------------------------------------------------------",
"Ran 51 tests in 54.457s",
"",
"OK (skipped=1)"
],
"output_sha256": "28f0a1224bc1d46a43b8faad062d463820003eb8fd3b4c688f06cd8173380a22",
"receipt_sha256": "ebaf7682157339ba8813c1ff743a25fa6c0a85eedd8e06cdba3d22c208c242e8",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/ebaf7682157339ba8813c1ff743a25fa6c0a85eedd8e06cdba3d22c208c242e8.json"
}
```
## 闭合记录(合入前折叠)
- 本卡在 PR #683(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/683)合入前闭合并归档,随该 PR 一同合入。
- 该 PR 的合并提交即本任务的落地记录。
- 合入后无需回填,也无需单独的闭合 PR。
@@ -0,0 +1,90 @@
# Independent Code Review — feature/agent-stack-audit-remediation-b1
- Reviewer: independent CLI review session (agent-stack-b1-independent-review unit)
- Date: 2026-10-21
- Review object: Dev worktree branch `feature/agent-stack-audit-remediation-b1` @ `d55e57dd` (merged tip) versus main @ `b25846d2`; five pinned components diffed old-pin → new-pin:
- deps/agent-native `2fc66be6` → `ac91b843`
- deps/agent-squad `c7121bda` → `0f1daa8b`
- deps/agent-intent `651a2e1e` → `765a7e71`
- deps/agent-command `4f583ca3` → `fe7987dc`
- modules/backend `528e3a4c` → `b6141800`
- Plan: docs/superpowers/plans/2026-10-21-agent-stack-audit-remediation-plan.md (audit baseline d9e9c0560a)
- Claimed scope: batch 1 = U1, U2, U3a/U3b, U4, U5, U6, U7a/U7b, U10.
- Method: read-only review of `git diff` per component plus Dev-level files; focused package tests where this worktree permits (see Verification runs). This review made no changes other than this report file.
## Findings
### Blockers
None.
### Major
None.
### Minor
- **M1 — SessionScope identity string can collide (native).** `adapters/native/contract_bind.go:60-66` builds the scope identity as `tenant+"/"+project+"/"+session`. Distinct scopes containing `/` can collide (tenant `a/b` + project `c` vs tenant `a` + project `b/c`), which would bind two identities to the same hashed transcript file (contract_bind.go:72-77) and pass the exact-match check in `bindSessionScope` (adapters/native/backend.go:645-669). Today product identifiers do not contain `/`, so no legal path regresses; recommend length-prefixed or JSON-encoded identity in a follow-up.
- **M2 — Reflection/JSON "probe" of statically present fields (squad).** `adapters/native/register.go:217-247` marshals the native result through JSON to discover `SideEffects/Canceled/Aborted`. The squad module is pinned to native `ac91b843`, where these fields exist at compile time; direct field copy is identical, simpler, and the "deps 尚未跟进" rationale in the comment cannot occur in a statically linked Go build. Behaviorally equivalent today; flagged as avoidable indirection that can silently mask future field renames (JSON key drift would quietly fall back to "possible").
- **M3 — `trustedUnknownOpID` does not survive JSON roundtrip (command).** `result.go:44-49` (unexported flag) + `result.go:76-95` (Validate): an Unknown receipt carrying a trusted OperationID fails `Validate()` after a marshal/unmarshal cycle. Verified by grep that no in-repo path re-validates a deserialized receipt (backend persists audit *envelopes*, not receipts; `internal/cmdbus` has no ExecReceipt unmarshal), so nothing breaks today — but the library contract is sharper than its docs admit. Suggest documenting the roundtrip limitation or providing a rehydration constructor.
- **M4 — Stale recovery header comment (squad).** `squad/engine.go:1053-1069` still states "auto 允许一次重试", but `HitlAuto` now falls into the `default:` reconciliation branch (engine.go:1137-1178) and no longer auto-retries running steps without a trusted record. The code is the intended F05 tightening; only the comment contradicts it.
- **M5 — Writable preflight does not cover `delegate_task` (native).** U2 preflight (adapters/native/backend.go:213-227) rejects non-empty Writable with exec/MCP/host tools, but the workspace registry still registers `subagent.Register` (backend.go:278) and code orchestration (`run_tool_script`). `run_tool_script` only invokes registry tools (which enforce the allowlist, runtime/tools/registry.go:369-388) and bans the interpreter, so the residual exposure is a spawned sub-agent not inheriting the parent's write-set. The plan scoped this batch's preflight to "进程/MCP/宿主工具"; a follow-up decision on subagent scoping is warranted, not a blocker here.
- **M6 — Canceled mapping keyed on ctx, not on the result (native).** `adapters/native/contract.go:100-105` maps to `OutcomeCanceled` only via `errors.Is(ctx.Err(), context.Canceled)`. A `runRes.Canceled`/`StopAborted` run that is *not* caused by ctx cancellation falls to the failed branch (with `Retryable=false` because backend.go:385-396 already clears it, so no unsafe retry — only the outcome label is less precise). Edge case; native abort paths set ctx.
### Notes
- **N1 — Squad `AGENT-EXECUTION.md` disables cursor/antigravity adapters** (component diff, +10/-2). Operational agent-execution config, not product code; documented on the task card with fail-stop rationale. It will ride the component branch into the component repo — restore `enabled: true` as the file itself instructs.
- **N2 — Command `AGENT-EXECUTION.md` added (+67).** Host-documented deviation (component lacked one; template parity with agent-intent), recorded on the task card.
- **N3 — Executor failures without `SideEffects=none` lose auto-retry.** `squad/workflow.go:455-459` + `squad/port.go:99-117` intentionally default missing qualifier to "possible" (no bounded retry). This is the specified KTD4/U3 behavior change ("缺少副作用证明的失败默认禁止自动重放"), covered by `TestSideEffectsPossibleNotRetried` / `TestSideEffectsNoneRetriedWithinBounds`; third-party executors must now declare `none` to keep transient retry. Listed so the API change is visible to integrators.
- **N4 — Generated-command ActionRef now resolves against the full snapshot**, not only the top-k catalog (`intent/recognizer.go:384-437` `resolveActionRef`, replacing `generatedSpecOf`). Deliberate widening that fixes F08's "合法异名目标被拒"; slot keys/values and dispatch still gated by `validateProvidedSlots` (intent/slots.go:174-214) and backend admission (internal/server/intent_admission.go:41-63).
- **N5 — Pin coherence verified.** Nested gitlinks agree at every level: intent `765a7e71` (native→deps, squad→deps, backend→deps), native `ac91b843` (squad→deps, backend→deps), command `fe7987dc` (backend→deps), squad `0f1daa8b` (backend→deps).
- **N6 — Audit-failure unknown receipts keep the trusted ID** via `unknownReceipt`/`NewUnknownAfterExecution` (internal/cmdbus/cmdbus.go:690-712, deps/agent-command/result.go:64-71); rejected never carries an ID (cmdbus.go:706-712). Confirms U7b's F10 requirement end-to-end with `TestCommittedWithAuditFailureKeepsOperationID`.
## Unit-by-unit review (spec lens, with trigger-condition evidence)
| Unit / Finding | Trigger in plan | Blocking change in diff (file:line) | Verdict |
|---|---|---|---|
| U1 / F01 | reasoning Targets with `../`, absolute, or escaping symlink wrote files before delivery validation | Authorization pass before any write: `pathInWriteSet` + `tools.Resolve` per target, then a second write loop (adapters/native/backend.go:336-378); zero-write asserted by `TestBackendReasoningProfileRejectsPathEscape` (traversal/absolute/symlink, safe.md also absent) | blocked |
| U1 / F03 | ExecConfig env nil → host env inherited | `normalizeExecEnv` nil→empty whitelist at registration and per-process (runtime/tools/exec.go:42-51, 165); tests nil/explicit-empty/whitelist | blocked |
| U1 / F15a | CombinedOutput unbounded before 64KiB trim | Continuous drain into fixed head+tail ring (`headTailBuffer`, exec.go:192-276; `runProcess` 157-175); bounded-memory + cancel-drain tests | blocked |
| U2 / F02 | Same WorkDir reused across SessionScopes; Writable set on workspace | Scope-keyed transcript `transcriptPathForScope` (contract_bind.go:72-77); fail-closed identity binding incl. refusal to adopt identity-less stores (backend.go:645-669); Writable enforced on write/edit/multi_edit (runtime/tools/registry.go:356-388, builtin.go:106-109/155-158/339-342) with exec/MCP/host preflight rejection (backend.go:213-227); empty scope keeps legacy path (compat verified) | blocked |
| U2 / F04 | Torn JSONL tail then append → mid-file corruption | `repairTailLocked` final-line-only repair, valid fragment gets newline, invalid truncated, mid-file untouched (runtime/session/tree.go:156-234); torn/missing-newline/mid-file/append-then-load tests | blocked |
| U2 / F15b | CapText declared but Text empty | Contract captures final assistant text and surfaces it; pure-text success no longer forces implicit file delivery (`withAllowEmptyTargets`, contract.go:62-66, 88-96); `TestContractPureTextSuccessCarriesText` | blocked |
| U3a | canceled treated as retryable; side-effect honesty | Result gains Canceled/Unknown/SideEffects with `EffectiveSideEffects` (exec/backend.go:79-105); loop propagates ToolCalls and Canceled, clears Retryable on cancel (runtime/loop.go:202-213, 343-361); adapter maps definite-preflight → none, tool dispatch → possible, run-started transport failure → Unknown+possible (backend.go:334-341, 381-409, sideEffectsForTools 593-599) | blocked |
| U3b / F05 | success=patches-only; failed-with-patches promoted; file-presence completion on recovery; new ExecutionID per retry | `(nil,nil)`/nil-result stable failure (workflow.go:429-439); promotion path removed — patches materialize only after explicit Success, or as diagnostics without flipping status (workflow.go:444-461); canceled/aborted never retried (workflow.go:440-452, port.go:121-123); recovery trusts only `step_terminals.json` records, otherwise `needs_reconciliation` (engine.go:1070-1180, models.go:299-314); `RunWaitingReconciliation` resume inspects without advancing (engine.go:1199-1206); ExecutionID reused across attempts and recovery (workflow.go:402-409, engine.go:1130-1133; `TestRetryReusesExecutionIDAndDeadline`) | blocked |
| U4 / F06 | config update reuses stale executor cache | Cache key adds `ConfigIdentity(role)` + factory generation (squad/backend.go:171-175, config_identity.go:14-33 incl. BashPermission); Register/RegisterInstance invalidate per-backend cache (backend.go:62-75); concurrency gates persist across generations and update max on resolve (backend.go:182-285); allow→deny / factory swap / gate-cap tests | blocked |
| U5 / F07(intent) | caller mutates registry input or returned nested objects | Deep clone at Register/compile/Snapshot/Options/Catalog boundaries (intent/fold.go:57-129, registry.go:488, 520-528, 554, 588-592, catalog.go:36-60,142); isolation asserted in u5 tests | blocked |
| U5 / F08 | generated fallback bypasses slot validation / loses risk metadata; valid alias rejected | Generated Top built from authoritative compiledSpec with full slot validation, missing/invalid projection, risk/confirm/action metadata (recognizer.go:384-437, slots.go:174-214); classifier scores NaN/Inf/out-of-range rejected and quantized before threshold (recognizer.go:176-201, 322-330); non-finite/low-confidence keep ambiguous | blocked |
| U6 / F09 | InvalidSlots dropped; router only sees Start; text-only params | Gate projects Missing *and* Invalid with reasons + risk/confirm/action (intentgate/gate.go:42-52, 198-232); router blocks dispatch unless `Complete()` and structured Clarification (route/route.go:60-72, 173-180); unsupported cancel/set_slot honestly fall back to turn without dispatching same-sentence Start (route/route.go:83-90, 121-131); params mapped per declared schema with type casts and required-param admission (server/intent_admission.go:41-113); high-risk still confirmed by cmdbus (`TestHighRiskCommandStillRequiresConfirmation`) | blocked |
| U7a / F07(command) | ParamsSchema/ExposeAsTool shared references | Deep copy in/out of Register/Resolve/List (registry.go:37-47, 62-66, 117, 124-130); mutation conformance test | blocked |
| U7b / F10 | any handler error → "no side effects rejected"; audit failure drops op ID | `ErrNotExecuted`/`NotExecuted()` marker; unmarked handler errors → Unknown with trusted op ID (cmdbus.go:75-118, 536-553); audit-failure path preserves ID for committed/accepted/unknown and never for rejected (cmdbus.go:687-712); confirm-card unknown lands warn terminal with reconciliation note (cmdbus.go:615-640); owner/project-scoped bounded operation query + endpoint (cmdbus.go:263-296, server/operation_query.go) | blocked |
| U10 / F13 | squad resolves model/base/key from default account/project | Factory resolves entire route from the run's effective snapshot + profile via the same `ResolveChatModel`/`OpenRouteClient` rules as direct path (app/squad_native.go:57-84, runtime/run_config.go:17-62, squad_runner.go:83-86, llm.go:255/604); fallback to default only when host lacks the Settings port; owner-crossing secret tests (`TestSquadNativeOptionsNeverBorrowsAnotherOwnerSecret`), route-contract EOF parity test | blocked |
## Regression lens
- **Empty SessionScope:** legacy fixed transcript path, no identity checks, `Targets` defaulting preserved (backend.go:144-146, contract_bind.go:72-74). Verified by `TestLegacyTranscriptStillLoads` and empty-scope branches in regression tests.
- **Zero-value results:** `ExecResult{}` zero value = Success false, SideEffects empty → read as "possible" (port.go:110-117) → stable failure, no panic (`TestNilNilResultIsStableFailure`, `TestSideEffectsZeroMeansLegacy`).
- **Default configs:** U10 keeps default-account fallback only for hosts without the run Settings port (squad_native.go:60-66); `routeContractFor` logic untouched, only re-exported (`llm.go:604`).
- **Retry semantics change is intentional and covered** (Note N3): transport failures from executors that do not declare `SideEffects=none` now fail fast instead of text-heuristic retry — exactly the F05/KTD4 remedy.
- **Route fallback for legal matched intents:** `unconsumedCommandKinds` only fires when cancel/set_slot/chitchat/handoff commands are present; the rule funnel emits those only on expectation-hit/ambiguous branches, and matched+complete emits Start alone (intent/command.go:88-113), so normal command dispatch is unaffected (`TestValidSlotsReachHandlerMappedPerSchema`).
## Scope lens
All component diffs map to declared units: native (U1/U2/U3a), squad (U3b/U4), intent (U5, generator.go comment-only), command (U7a), backend (U6/U7b/U10 + mechanical `OpenRouteClient` rename and nested gitlinks). Dev-level changes are the task card, the plan document, and the five gitlinks. Out-of-unit but documented: squad `AGENT-EXECUTION.md` adapter disablement and command `AGENT-EXECUTION.md` addition (Notes N1/N2 — execution-infra config, explicitly recorded on the task card; no product code outside units). No undeclared product behavior changes found.
## Test lens
New tests assert the original trigger conditions, not just happy paths: F01 asserts zero writes for all three escape classes; F02 asserts no shared transcript, resume-with-matching-scope, and refusal to adopt identity-less stores; F04 covers torn tail, missing newline, mid-file error, repair-then-append; F05 covers patches-only success, failed-with-full-patches stays failed, canceled/aborted not retried, side-effect-gated retry bounds, all four recovery quadrants (record/files × present/missing), ExecutionID+deadline reuse; F06 covers tighten/loosen/factory-swap/tool-change/cache-reuse/parallel-first/in-flight-cap; F07 mutation attempts on both registries; F08/F09 missing vs invalid vs unknown-key vs required-param blocks with reasons; F10 unknown-with-ID, not-executed-stays-rejected, audit-failure-keeps-ID, repeated-confirm-once, cross-owner query scope; F13 owner-secret isolation, project layer, direct-route parity, default fallback.
## Verification runs (this review worktree)
Focused tests with `GOWORK=off`, per-directory, foreground:
- deps/agent-command: `go test ./...` — ok (2 packages, includes new registry/result/conformance tests).
- deps/agent-native: `go test ./runtime/session/ ./runtime/tools/` — ok (torn-JSONL, env whitelist, bounded buffer, writable allowlist suites). `adapters/native` — NOT_RUN here: nested deps (error-code-system etc.) are uninitialized in this review worktree and cannot be materialized offline; covered by the recorded green combination run.
- deps/agent-intent, deps/agent-squad, modules/backend: NOT_RUN here for the same missing-nested-deps reason (backend imports all components). Covered by the host-recorded combination `make test` PASS on the batch candidate (verify-combo-make-test4.json, exit=0), which this review did not re-run per packet instruction.
## Verdict
REVIEW VERDICT: APPROVE
Blocking reasons: none. The diff implements the declared batch-1 units with trigger-condition regression coverage; the six minor findings (M1-M6) are quality follow-ups that do not block merge: none of them breaks a legal existing path, all have safe defaults (fail-closed), and the intentional behavior changes (retry tightening, cancel/no-retry semantics) are the specified F05 remedies with test coverage. Recommend filing M1-M5 as follow-up increments and restoring the temporarily disabled agent-execution adapters (N1) per the file's own instruction.
@@ -0,0 +1,94 @@
# 独立代码审查:fix/multi-instance-test-failure-diagnostics(多实例双进程验收的测试卫生修复)
- 审查对象:`5360884e9283d962caf945d944df05b5e5059177..2eaa7510848ba1fc13013f5b0a9ff1fd548b2071`(R0..HEAD,分支 fix/multi-instance-test-failure-diagnostics;测试侧变更落在 transport 提交 2eaa751)
- 涉及文件(审查范围内):仅 `tests/integration/multi-instance/multi_instance_test.go`(+95/−17);范围内的另一文件 `.task/fix-multi-instance-test-failure-diagnostics.md` 属任务卡豁免范围,不在本审查内
- 审查者身份:全新会话独立审查,未参与实现;本会话除本报告外不写入任何文件,全部只读
- 背景核对:范围内 `git diff 5360884e92..HEAD -- Makefile .gitea/` 为空(0 行)——包级 `go test -count=1 -timeout 15m .`(Makefile:190)未被改动
## 结论:PASS(无阻塞项;6 项非阻塞发现)
两处修复意图均达成:owner_crash_takeover 的轮询从「到点静默退出 → POST 落到误导性 409」改为显式 `reconciled` 硬要求 + 定点失败;restart_invariants 在断言前先等恢复视图。逐条核对未发现断言被削弱,未发现超时预算放大,未发现实际的 e.a/e.b 解引用风险。
---
## 一、断言是否被削弱(重点 1)
逐 hunk 核对新旧文件(旧:5360884e92 版,新:HEAD 版):
1. **cross_event_sse**:仅 `t.Fatal(...)` → `t.Fatalf(..., lastSeq, instanceOf)`,select 的 8s 预算与事件分支条件(`!ok`、`ev.seq <= lastSeq`)逐字未动。**等价,仅增强诊断。**
2. **owner_crash_takeover 轮询**:旧代码 `break` 后落到 POST,超时静默继续;新代码引入 `reconciled` 标志,未观察到即 `t.Fatalf`。判定语义与旧版一致(最后一个 run 的 status == "interrupted",经 `runStatuses` 取末位;类型断言失败时旧为 nil map 比较、新为空串,均判 false,等价)。**这是收紧而非削弱**:旧版存在一个能「未观察 interrupted 仍通过」的窄缝——最后一次 300ms 轮询之后、POST 之前对账恰好收口,POST 得 200 而通过,测试从未显式观察到 interrupted;新版关闭该缝,通过必须以观察到 interrupted 为前提。轮询粒度(300ms)与预算(`httpWait`=15s)不变。
3. **restart_invariants**:新增前置等待,其后的既有断言(`msgs >= 5`、`lastSeq != 0`、SSE 全量重放 seq 严格连续、重启后 postMessage)逐字未动。**无削弱。**
4. **waitConversationIdle / waitActiveHITL / waitRunStatus / waitHealthy**:成功条件逐字等价(idle 的 busy 白名单 pending/running/waiting_hitl 不变;HITL 的 `ok && len>0 && id!="" && runID!=""` 不变;waitRunStatus 的 `s == status` 不变;waitHealthy 的 200/401 不变);改动仅是新增 statuses/cardCount/observed/last 探针跟踪与更详细消息。`runStatuses` 把 `runs, ok := …` 的 ok 守卫换为 `runs, _ := …`,缺失/类型不符时返回空切片,与旧「ok=false 则不进循环」语义一致。
5. **新增等待是否掩盖真实缺陷**:核对了「run 卡在 running」这一类真 bug 的走向——新等待会超时并在 Fatalf 中给出完整 statuses 列表(含卡住的 running),缺陷以测试失败形式暴露且可定位;旧代码同场景会落到后面 postMessage 的 `waitConversationIdle(20s)` 以笼统的 "still busy" 失败。**不存在把失败变成通过或无限等待的路径**(所有等待都有界且超时即 Fatalf)。唯一「弱化形状」见第二节发现 1:恢复视图判据的 interrupted 半边被历史 run 平凡满足,对「B 重启对账」回归不敏感——但该回归在改动前同样不被任何断言覆盖,覆盖无回退。
## 二、新增失败路径的信息是否足以定位(重点 2)
逐处核对新增 Fatalf 的字段:
- owner_crash_takeover:点名被轮询实例(`instanceOf(uc.api)` → "miA (http://addr)")、实际耗时 vs 预算(15s)、完整 statuses 序列、重启后 A 的日志。**足以定位。**
- restart_invariants:点名轮询实例(miB)、耗时/预算、statuses、logA+logB 双日志。**足以定位。**
- waitConversationIdle / waitActiveHITL / waitRunStatus / waitHealthy / 两处 SSE 超时:均点名实例与地址、给出耗时 vs 预算与最后观测值(statuses / cardCount / observed status / last probe / 期望 seq)。**充分。**
非阻塞发现:
- **发现 1(判据语义弱于注释声明)**:restart_invariants 等待注释称「对账收口的 interrupted run 可见」,但此刻会话里的 interrupted run 来自 owner_crash_takeover 中 **A** 重启时对 A 遗留 run 的收口(该子测试已硬性要求其存在并保持终态),而 B 被杀时并未持有任何 in-flight run(自 owner_crash_takeover 起全部活动在 A 上),**B 的重启对账在本场景无事可做**。因此该判据实际语义是「会话无 pending/running/waiting_hitl(且历史上存在 interrupted run)」,不能检出「B 启动对账回归」类缺陷。无害但注释高估了判据的覆盖面;且与 owner_crash_takeover 存在顺序耦合——未来若删改该子测试,本等待将在正常路径必然空转 15s 后失败(见发现 5 的脆弱性)。
- **发现 2(非阻塞)**:waitActiveHITL / waitRunStatus 轮询 uc.apiB 时消息正确点名 B,但日志只附 logA;被轮询实例 B 的日志往往更相关。与改动前「只附 logA」的旧风格一致,非回归,建议后续补 logB。
- **发现 3(非阻塞)**:restart_invariants 失败路径的 logA 是 owner_crash_takeover 重启后的 A 的日志;原 A(持有崩溃 run 时期)的日志在重启时已丢弃。此处崩溃 run 早已收口,影响有限,仅作知悉。
## 三、恢复视图判据本身是否正确(重点 3)
判据:∃ status == "interrupted" 且 ∀ status ∉ {pending, running, waiting_hitl}。
- **正常路径误判为未恢复的风险**:在本测试流内不存在——interrupted run 由 owner_crash_takeover 硬性保证存在且终态不可逆;唯一可能 busy 的 run 是「A resumes after restart」(mock runner,秒级收口),15s 预算充裕。当前流内**无误判风险**;跨子测试顺序耦合的脆弱性见发现 1/5。
- **真实卡死误判为已恢复的风险**:卡死的 run 必处于 pending/running/waiting_hitl 之一 → busy=true → 不会误判。**但该 busy 集是白名单**:若 backend 未来引入新的非终态 status 字符串(如 finalizing/cancelling 之类),白名单外的非终态会被当作空闲而误判已恢复。这是与 waitConversationIdle 完全相同的既有语义(非本次引入),两处共享该潜在弱点。status 词汇表(pending/running/waiting_hitl/completed/failed/interrupted/cancelled)取自测试自身与改动前代码,未对照 backend 源码核验,**[未经证实]**。
- 轮询经 `e.do`:非 200(含启动后瞬态 5xx)直接 Fatalf 终止轮询——与改动前该子测试的单次 GET 行为一致,失败是响亮且有信息的,可接受。
## 四、e.a/e.b 解引用风险(重点 4)
- `instanceOf`:先 `e.baseFor(c)` 再比较。`baseFor` 在 e.uc 非空且 c 为 apiB/streamB 时即解引用 `e.b.base`——因此 `instanceOf` 里的 `e.b != nil` 守卫实际不可达(e.b 为 nil 时 baseFor 先行 panic)。**实践无风险**:e.b 由 startPair 在任何 client 使用前赋值,两处重启均为非空重赋值;守卫属无害死代码(发现 4,非阻塞)。
- base 串等值判定:`freePort` 保证 A/B 地址不同,且重启复用原 addr 字符串(`strings.TrimPrefix(e.b.base, "http://")`),重启后 `instanceOf` 对 apiB/streamB 仍正确映射到**新的** B。无误判面。
- restart_invariants 中 `killNine(e.b)` → `startInstance` 之后 **e.a 仍有效**:e.a 未被触碰,指向 owner_crash_takeover 重启后的存活 A 进程;`uc.api` 经 baseFor 取 `e.a.base` 正确;`e.a.log` 是当前 A 实例的 lockedBuffer,非悬垂。无解引用风险。
- `runStatuses` 对 nil map / 缺键 / 元素非 map 全部安全(带 ok 断言),失败路径打印不会引入二次 panic。
## 五、超时预算与包级 timeout(重点 5)
- `httpWait`(15s)、waitActiveHITL(15s)、waitHealthy(60s)、两处 SSE select(8s/15s)、waitConversationIdle 调用方(20s)全部未动。新增的 restart_invariants 等待复用 `httpWait`(≤15s,正常路径一次性成本),owner_crash_takeover 失败路径与旧版同界(旧:15s 空转 + POST 409;新:≤15s 定点失败)。**无预算悄悄放大。**
- 包级 `go test -count=1 -timeout 15m .`(Makefile:190,由 CI test-pg job 调用)不在 diff 内:`git diff 5360884e92..HEAD -- Makefile .gitea/` 为 0 行。**未被改动。**
## 六、检测器可失败性验证(packet 约束)
按约束在 /tmp 构造 3 个测试语料未覆盖的早退读取器形态,直接调用 `tests.test_shell_pipe_hazard.hazards()` 并以真实 bash(`set -euo pipefail`)验证危害性:
| 形态(均不在既有 12 个测试的语料内) | hazards() | bash 实测 |
|---|---|---|
| `seq 1 200000 \| sed '5q;d'`(数字地址前缀的 sed q) | `[(1, "sed '5q;d'")]` 命中 | rc=141,无 "survived" |
| `seq 1 200000 \| grep --max-count=3 1`(长选项 =值 形式) | `[(1, 'grep --max-count=3 1')]` 命中 | rc=141,无 "survived" |
| `seq 1 200000 \| nice -5 head -1`(nice 旧式捆绑 -5 包装 head) | `[(1, 'nice -5 head -1')]` 命中 | rc=141,无 "survived" |
检测器对全部 3 个新形态均报出,且实测确为 SIGPIPE(141)——**检测器真能失败,非阻塞项**。配套只读套件 `PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_shell_pipe_hazard`:Ran 12 tests, OK。探针脚本仅在 /tmp/mi-review-probe,仓库内无残留。
## 七、执行过的只读命令与结果(证据)
| 命令 | 结果 |
|---|---|
| `git status --porcelain=v2 --branch`、`git log --oneline -8` | detached HEAD 2eaa751;范围含 fd3ccd0(任务卡)、2eaa751(transport,含测试变更) |
| `git diff 5360884e92..HEAD --stat` / `--name-only` | 仅 2 文件:任务卡 + multi_instance_test.go(+95/−17) |
| `git diff 5360884e92..HEAD -- Makefile .gitea/ \| wc -l` | 0(包级 -timeout 15m 未动) |
| `git show 5360884e92:tests/…/multi_instance_test.go`(对照读) | 新旧逐 hunk 等价性核对依据 |
| `gofmt -l .`(tests/integration/multi-instance) | 无输出(格式干净) |
| `go vet .`(tests/integration/multi-instance) | exit 0,无输出(可编译可 vet) |
| `PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_shell_pipe_hazard` | Ran 12 tests, OK |
| /tmp/mi-review-probe/probe.py(hazards() + bash 141 实测) | 3/3 新形态命中,3/3 rc=141 |
**未验证项**:集成测试本体未运行(需 AGENT_PG_TEST_DSN + AGENT_BIN + 构建 backend,超出本只读审查范围)——两处修复在真实双进程下的运行时行为 NOT_RUN;status 词汇表未对照 backend 源码核验,标注 [未经证实]。
## 非阻塞发现汇总
1. restart_invariants 恢复视图判据的 interrupted 半边由历史 run 平凡满足,注释高估其覆盖面(不检出 B 重启对账回归;改动前同样不检出,覆盖无回退),并与 owner_crash_takeover 存在顺序耦合。
2. busy 白名单(pending/running/waiting_hitl)对 backend 新增非终态 status 不设防——与 waitConversationIdle 相同的既有共享弱点,非本次引入。
3. waitActiveHITL / waitRunStatus 轮询 B 时只附 logA,建议后续补 logB。
4. `instanceOf` 的 `e.b != nil` 守卫不可达(baseFor 已先行解引用);无害死代码。
5. restart_invariants 对 owner_crash_takeover 的顺序依赖:删改前者会使本等待在正常路径必然空转 15s 后失败(当前流内不触发)。
6. 轮询经 `e.do` 对任意非 200 立即 Fatalf(含瞬态 5xx)——与改动前行为一致的响亮失败,可接受。
以上均不阻塞交付;本次变更可以进入 Review/合入流程(合入门禁按 policy/delivery.md 另行满足)。
@@ -0,0 +1,311 @@
---
title: Agent Stack Audit and Remediation - Plan
type: fix
date: 2026-10-21
artifact_contract: ce-unified-plan/v1
product_contract_source: ce-plan-bootstrap
execution: code
---
# Agent Stack Audit and Remediation - Plan
## Goal Capsule
对 Agent-Native、Agent-Squad、Agent-Intent、Agent-Command 及其 Backend 接入做源码审查和外部协议核对,先修复现有能力的契约缺陷,再补充影响实际使用的功能。
本次交付是待用户审阅的方案;业务实现、迁移、部署和发布尚未授权或执行。
建议保留现有四库分层,分批兑现可验证结果,不重写 Agent 平台、不迁移到新的编排框架。
- 第一批:执行边界、失败结果、权限缓存、意图校验、命令回执、用户/项目配置。
- 第二批:持久化适配、队列限额、过程事件。
- 后续增量:多轮意图闭环、恢复诊断、目录召回、外部互通。
- 停止条件:批准批次的回归和适用组合验证通过,完成对应 Review、PR/CI 与收口;未批准增量不顺带实施。
## Product Contract
### Scope and Evidence
审查日期为 2026-10-21。以下 SHA 是本次实际检出;Dev 启动检查确认与当次远端 main 一致、组件无漂移,不能据此声称各组件远端没有更新。
| 对象 | 路径 | 审查 SHA |
|---|---|---|
| Dev | 仓库根 | `d9e9c0560a2767539f4415b579adbeff86a22ba6` |
| Native | `deps/agent-native` | `2fc66be65201232ea38ba64d457efad5565d869d` |
| Squad | `deps/agent-squad` | `c7121bda2a681dc46078350f7929e56e142db57f` |
| Intent | `deps/agent-intent` | `651a2e1e03c4326888ef9f742ce592140ae30ab8` |
| Command | `deps/agent-command` | `4f583ca3eeeb07a56a714c5086de84dcd6ad2980` |
| Backend | `modules/backend` | `f084d10aa5ea2e4ad2ab4e6eb1268d3e83c6e991` |
发现以当前源代码及现有测试的检查为依据,均给出触发条件;不是线上事故结论,也不是穷尽性安全认证。
研究子任务报告了 Native、Squad 的本地测试和仓外探针;主线程已核对关键源代码,但未重放这些执行,也未将其导入正式 evidence,故本方案不以这些报告作为通过凭证。
修复回归、组合测试、GATE、独立 REPLAY、生产验收均为 `NOT_RUN`。
旧 N1/N2/N3、Q1/Q2/Q3 和 I1/I2 提案只用于理解历史意图,不把旧“已完成”或“待实现”表述当作当前事实。
### Existing Responsibilities
| 模块 | 当前已有能力 | 应保持的边界 |
|---|---|---|
| Agent-Intent | 规则/守卫/预期、槽位验证、语义裁决、生成兜底、目录与诊断 | 识别和提出动作;不授予执行权限 |
| Agent-Command | 命令资产/注册表、危险分级、执行四态、审计信封 | 保持无 I/O;实际授权、执行、幂等持久化由宿主负责 |
| Agent-Native | 模型流、Agent 循环、工具/MCP、预算/取消、会话、checkpoint、外部适配 | 执行一次受约束任务;路径授权不冒充 OS 沙箱 |
| Agent-Squad | 规划、图调度、leader、HITL、黑板/邮箱/移交/任务板、存储与队列适配 | 协作与步骤推进;依赖注入执行器,不复制单 Agent 内核 |
这些库并非一条固定串行流水线:普通对话可以直接执行 Native,系统命令由宿主消费 Intent 并经 Command 受理,多步任务才进入 Squad。
真正的问题集中在边界传递和失败路径,而不是缺少更多拓扑或工具数量。
### Findings
严重级别按触发后的影响排序,生产暴露面另列。P1 不表示已观察到线上损害。
下列行号是本次基线定位,实施时还须按符号及新基线复核。
| ID | 优先级与触发条件 | 源码证据 | 后果与影响边界 |
|---|---|---|---|
| F01 | P1:reasoning 请求的 Targets 含 `../` 或指向根外的符号链接 | Native `adapters/native/backend.go:324-333` 先直接写文件,后续才做交付校验 | 可先写出工作区再返回失败;应在任何副作用前拒绝非法目标。上层自行校验可降低暴露,不能代替库契约 |
| F02 | P1:同一 WorkDir 被不同 ContractRequest.SessionScope 复用;或设置 Workspace.Writable | Native `adapters/native/contract.go:42-51` 未映射这些约束;`backend.go:260-273` 使用固定 transcript,仅记录字符串 scope | 跨 scope 上下文串读及写集约束失效。当前 Backend 未发现 Contract() 消费,不据此断言产品跨租户泄露 |
| F03 | P1:ExecConfig 已启用解释器但 Env 为 nil | Native `runtime/tools/exec.go:25-26,139` | nil 被交给 os/exec,继承宿主环境,与完整白名单合同不符。Backend 当前解释器装配显式给出 env,该具体入口已有缓解 |
| F04 | P1:崩溃留下半截 JSONL 尾行,随后续写 | Native `runtime/session/tree.go:155-181,194-207` | Load 只在内存忽略尾行,Append 紧接损坏字节;下一次读取变为中段 JSON 错误。完整 JSON 无换行也需处理 |
| F05 | P1:Executor 成功仅含 patches、失败带 patches,或 running 步骤崩溃后自动恢复 | Squad `squad/workflow.go:403-418,455-464`、`squad/engine.go:1055-1109` | 正常路径成功未物化而失败、失败可被提升为成功;恢复路径文件齐全即判 completed,缺文件则直接重跑,未核对可信终态和外部副作用。Native 自行落盘只能掩盖其中的成功物化问题 |
| F06 | P1:同名 role/model/backend 的权限或并发配置更新 | Squad `squad/backend.go:38-48,86-123`;`adapters/native/register.go:104-106` | 缓存键忽略 BashPermission/其他配置,Register 也不清缓存;allow→deny 后可能继续复用旧执行器。产品每次新建 resolver 可缓解,长期服务/公共库使用仍受影响 |
| F07 | P1:调用方修改注册输入或公开返回的嵌套对象 | Intent `intent/fold.go:37-57`、`registry.go:289,494,531,557`;Command `registry.go:60,98,107` | Intent.Action/Examples 与部分目录数据、Command.ParamsSchema/ExposeAsTool 共享引用;可在 revision/锁之外改变已验证定义。这是同进程 API 合同缺陷,不等于任意远端用户已能修改 |
| F08 | P1:启用意图生成兜底,并返回缺槽/非法槽值或异名 Action | Intent `intent/recognizer.go:333-414`、`catalog.go` | 生成路径未共用完整槽位校验,Top 丢风险/确认/动作元数据,handler 槽位查表键不一致;显式 Action 的合法异名目标又会被拒。Backend 开关 `runtime.intentGenerator` 默认 false |
| F09 | P1:规则识别到非法槽位,宿主收到 Start + Clarify | Backend `internal/intentgate/gate.go:167-178`、`internal/route/route.go:66-72`、`internal/server/server.go:1206-1230` | InvalidSlots 被丢弃,Router 只取 Start。命令入口还只传 text,未传已识别槽位;可能误路由或被后续 schema 拒绝。不能据此断言任何高危命令都能绕过 cmdbus 确认 |
| F10 | P1:handler 已有副作用后报错,或成功后审计存储失败 | Command `result.go:41-43`;Backend `internal/cmdbus/cmdbus.go:433-438,539-559` | 任意 handler error 被归为“无副作用 rejected”;审计失败改 unknown 却丢已生成 op ID,且库禁止 unknown 携带 ID,影响对账及安全重试 |
| F11 | P1/P2:使用 SQLiteStore 保存协作状态,或在 async/on_exit 模式退出时 checkpoint 失败 | Squad `adapters/sqlite/runstore.go:77-119`;`squad/workflow.go:41-53` | Mailbox/Handoffs/Board 未持久化;相同 JSON 文本作 map key 会覆盖解码目标;flush 错误被吞。Checkpoint 失败不等于已完成外部动作回滚,应单独报告持久化失败 |
| F12 | P2:同一 runtime 下多个 agent 同时或连续领取任务 | Squad `adapters/sqlite/queue.go:226-239` | 只数 agent 的在途任务,取两种 cap 的最小值不能限制 runtime 总并发;当前产品是否启用该独立队列须在实施时核验 |
| F13 | P1:当前用户/项目有独立模型路由配置,进入 Squad | Backend `internal/app/app.go:340-364`、`internal/runtime/squad_runner.go:83-95`、`internal/runtime/llm.go:604-608` | 小队从默认账户/空项目读取 base/key 等,入口只覆盖 model,可能把用户模型名与默认端点/凭据混用;ReasoningSplit、RouteContract 也与 direct 路径不一致 |
| F14 | P2:小队同名工具多次调用,或工具返回错误 | Native `runtime/loop.go:43-45,777-836`、`adapters/native/backend.go:405-418`;Squad `squad/port.go:66`;Backend `internal/runtime/squad_runner.go:49-54` | CallID 在旧过程契约中丢失,宿主用工具名代替;结束事件实际文本在 Text,宿主读 Detail 并恒写 ok。结果归属、摘要与成功状态失真 |
| F15 | P2:执行工具输出大量数据;或 Contract 调用要求纯文本 | Native `runtime/tools/exec.go:149,175`、`adapters/native/contract.go:25,67-77` | CombinedOutput 先全量缓冲再裁剪,64KiB 只是展示上限;声明 CapText 却不填 Text。分别并入 U1、U2 的合同修复 |
| F16 | P2:使用 A2A adapter 接入符合 1.0 的外部服务 | Squad `adapters/a2a/card.go:24-46`、`executor.go:19-25,135,171`、`conformance_test.go` | 声称 1.0,却仍用旧 `message/send`、`tasks/get`、小写状态与旧 Card;测试假服务复制了同一套自定义结构。当前 Backend 无该依赖,不阻塞本地核心修复,但不得声称 1.0 互通已验证 |
### Requirements
**执行与权限**
- R1. 不合法路径/写集/会话身份必须在模型或工具副作用发生前拒绝;支持空约束的既有兼容语义。
- R2. 后续运行采用新的权限配置;不能因旧缓存或错误配置作用域扩大能力或混用用户凭据。
- R3. 失败、取消、不确定、已完成保持真实语义;文件存在不是成功证据,未知不能无条件重试。
**输入、恢复与可核对性**
- R4. 规则与生成路径共用权威动作定义和槽位验证;缺失/非法/歧义必须保留到宿主。
- R5. 状态快照和注册表对外隔离可变数据,读者不能绕过更新协议改变定义。
- R6. 恢复后保留协作状态,保存失败可见;runtime/agent 并发上限独立生效。
- R7. 回执保留可信关联标识;事件保留工具调用身份、真实错误和实际输出;用户可分辨完成、等待与待核对。
**功能增量**
- R8. 在已批准修复收口后,单独选择下一批功能;不把提案中的全部建议自动视为实施范围。
---
## Planning Contract
### Technical Decisions
- KTD1. 保留四库边界和现有 Go 模块;通过当前接口增量修复。现有功能覆盖足够广,整体重写会扩大兼容性与验证成本。
- KTD2. 识别输出只作为动作建议。执行时按当前 actor/project 授权、命令 schema、确认凭证再判断;目录可见不代表允许执行。MCP 对工具 annotations 的不可信要求也支持这一边界,见 S1。
- KTD3. 以宿主当前运行的有效配置构造 model/base/key/协议及完成性规则,作为同一配置快照传入;不随意开通新的 provider 协议。工具权限可在执行前进一步收紧,不能由旧构造缓存恢复。
- KTD4. 先验证执行结果,再物化候选文件,再做交付检查;失败候选可以作为诊断保留,但不得发布为成功。结果 Unknown 与可重试性分别判断,不把 HTTP/网络错误等同于“尚未执行”。
- KTD5. Native 会话和 Squad 协作状态分别在原有存储上修复。检查点、工具副作用和产品聊天历史是不同记录;不新增统一分布式执行平台,不承诺跨外部系统 exactly-once。恢复设计参考 S2 的线程状态/跨线程存储区分。
- KTD6. Command 保持无 I/O。扩展可表达副作用不确定性的 handler 结果,并复用宿主已有 operation/outbox 存储;unknown 可携带已可信生成的 ID。可查询操作记录与权限检查必须真实存在,不能只改枚举注释。
- KTD7. 两个四态合同不强行合并:Command 描述动作受理/生效,Native 描述 Agent 执行终态;Squad 通过适配保留差异。命令 accepted 不是 Agent succeeded,创建确认卡也不是业务动作完成。
- KTD8. A2A 先明确支持版本并以官方独立 fixture 验证;需要兼容旧协议时显式分版本,不把旧协议重新标成 1.0。是否投入完整互通是后续增量决策,见 S3。
### Component Flow
```mermaid
flowchart TB
U[用户输入] --> H[Backend 身份与项目上下文]
H --> I[Intent 识别与槽位验证]
I --> C[Command 宿主授权与确认]
I --> W[工作流或 Squad 编排]
I --> N[Native 单 Agent 执行]
W --> N
N --> T[经授权工具与外部能力]
C --> O[操作记录与回执]
T --> E[真实结果与过程事件]
W --> S[协作状态与 checkpoint]
E --> H
O --> H
S --> H
```
这张图表达边界关系,不要求所有输入都逐一经过四个库。
### Outcome Flow
```mermaid
flowchart TB
R[请求] --> V{授权与参数有效}
V -->|否| X[Rejected: 未开始动作]
V -->|是| P{需要确认或异步处理}
P -->|是| A[Accepted: 保留可查询 ID]
P -->|否| E[执行动作]
A -->|授权确认或继续| E
E --> F{结果及记录是否确定}
F -->|已完成并可核对| C[Committed 或执行 Succeeded]
F -->|明确失败| D[Failed 或有依据的 Rejected]
F -->|无法确认| Q[Unknown: 保留 ID 与待核对原因]
Q --> Z[查询与对账: 禁止盲目重放]
```
取消请求同样需要确认执行侧状态;不能只因为本地 context 结束就声称远端动作已停止。
### Delivery Order
第一批建议 U1-U7、U10:其中 U1-U4 先修 Native/Squad 执行与恢复基础,U5-U7 处理输入/命令合同;U10 的当前用户/项目配置问题与它们并行处理,不排在可选存储适配之后。
第二批 U8、U9、U11:恢复适配、队列和事件一致性。
每个批次在组件兼容提交落地后更新 Backend 嵌套 gitlink 和 Dev 组合指针,完成一次适用组合验证。
同批只读研究可以并行;每个仓库写入单元按 `AGENT-EXECUTION.md` 新 session、受控 transport/admit 进入候选,由 host 验证与交付。
实施前重新确认当前代码、在途任务与远端分流;本文不会覆盖后续已修复问题,也不会复用过期 CI 结论。
迁移脚本可以在隔离测试库验证,真实数据迁移、部署和发布另走授权边界。
---
## Implementation Units
### U1. Native Workspace and Process Boundaries
目标:修复 F01/F03 及 F15 的输出缓冲问题,落实 R1。
文件:`deps/agent-native/adapters/native/backend.go`、`runtime/tools/exec.go`、现有 `runtime/tools/jail.go`/`atomic.go`;测试放 `adapters/native/backend_test.go`、`runtime/tools/exec_tools_test.go`。
方案:复用已有工作区路径解析,目标在执行前预检,实际访问仍校验根内与 symlink;Env nil 归一为空白名单;子进程持续 drain 到有界头尾缓冲,不在结束后才限制内存。
验收:合法目标正常产出;`../`、绝对路径和 symlink 逃逸在零外部写入前拒绝;nil/空/显式 env 分别验证;大输出与取消后子进程退出且缓冲有界。
不把工作区路径检查描述为完整 OS 沙箱;并发敌对文件系统场景优先复用可提供根约束的文件 API。
### U2. Native Session and Contract Fidelity
目标:修复 F02/F04 及 F15 的 CapText,落实 R1/R3/R6;依赖 U1 的文件访问边界。
文件:`deps/agent-native/adapters/native/contract.go`、`backend.go`、`runtime/session/tree.go`、`exec/contract_spec.go`;测试放 `runtime/session/tree_test.go`、`adapters/native/contract_regression_test.go`(新增)及 `conformance/`。
方案:将非空 SessionScope 绑定存储身份并在恢复前检查,Writable 限制贯穿可约束的文件工具;对于进程/MCP/宿主工具等无法兑现该写集的配置,本批在 preflight 明确拒绝非空 Writable,不假定根路径检查能限制任意进程。独占追加时修复半截尾记录/补齐合法无换行记录;中段损坏仍报错。保留真实文本输出,纯文本任务不强制隐式文件交付。
验收:同 root 不同 tenant/project/session 不串上下文;已有匹配 scope 可继续;身份不明的旧文件不得自动归给新身份;只允许写指定路径;半截尾行恢复后可再读再写;纯文本成功的 Text 非空。
兼容:空 scope 保持既有一次性/旧门面语义,不进行全库存量会话迁移;历史缺身份记录只在明确兼容路径读取。
### U3. Squad Execution Results
目标:修复 F05,落实 R3;无先行依赖。
文件:`deps/agent-squad/squad/workflow.go`、`leader.go`、`engine.go` 的 recoverInterrupted、`port.go`、步骤状态持久化及 `step_retry.go`、`adapters/native/register.go`;必要的 `deps/agent-native/exec/backend.go` 与适配器映射;测试 `squad/step_retry_test.go`、`squad/recovery_test.go`、`squad/attempt_test.go`、`squad/executor_result_contract_test.go`(新增)。
方案:按 KTD4 统一普通步、IntentRunner 和 leader 委派的结果处理;显式处理 `(nil,nil)` 与物化错误。已有文件只能帮助诊断,不能覆盖执行器终态。
当前旧 ExecResult/exec.Result 只有 Success/Retryable,不能承接 Unknown/SideEffects。本单元先以兼容新增的可选字段或等价的类型化错误保留这些信息,再让 retry 层消费;缺少副作用证明的失败默认禁止自动重放,不能继续只按错误文本推断可重试。已有明确未派发或纯读取失败仍可有界重试;无需在本批整体替换所有 Executor 接口。
恢复入口同样消费持久化的终态与副作用证据。running 崩溃后,只有可信完成记录才能恢复为 completed,只有明确未执行或安全重放证据才能转 pending;其他情况保留待核对原因,禁止自动推进,沿现有中断恢复/HITL 入口处理。无交互模式明确返回需核对状态,不无限等待,也不伪称动作已失败。
验收:成功 patches-only 正常完成;失败带完整 patches 仍失败;空结果稳定失败而不 panic;部分/非法 patches 不成功;取消/Unknown 不自动变成可重试失败。外部动作完成、终态未保存时崩溃,分别测试目标文件齐全/缺失,两种情况均不伪成功或自动重复动作。检查重复尝试仍复用 ExecutionID 和截止时间。
### U4. Squad Resolver Configuration
目标:修复 F06,落实 R2;无先行依赖。
文件:`deps/agent-squad/squad/backend.go`、`adapters/native/register.go`;测试 `squad/backend_test.go`、`adapters/native/register_test.go`。
方案:后端工厂和角色执行配置有明确版本/摘要,更新时使相关缓存失效;限流身份与执行器配置身份分开,不能换版本便绕过同一角色并发上限。
验收:allow→deny 收紧、deny→allow 显式放开、工厂替换、工具配置变化和限额变化正确;并行首次 Resolve 不重复构造;在途旧执行不会因新版本开启而突破总限额。
在途任务已开始动作不承诺回滚;需要即时撤权的宿主继续在每次工具执行前检查。
### U5. Intent Snapshot and Generated Actions
目标:修复 F07 的 Intent 部分与 F08,落实 R4/R5;无先行依赖。
文件:`deps/agent-intent/intent/fold.go`、`registry.go`、`catalog.go`、`recognizer.go`、`slots.go`;测试 `intent/registry_test.go`、`intent/slots_catalog_test.go`、`intent/i2_test.go`。
方案:补齐现有 clone 的输入/输出边界;目录保留权威 ActionRef;生成命令先解析回唯一 spec,再复用槽位校验和风险元数据;不得用 handler 字符串索引 spec-ID 槽位表。分类器输入同样隔离,拒绝非有限/越界分数并重检阈值。
验收:修改原始 spec、SpecByID/Catalog/Options 不影响生效快照;规则与生成同动作同槽位得到等价结果;必填缺失、非法枚举/格式、未知槽位及 handler 重名均稳定处理;Action 指向不同命令 ID 仍可正确路由;低置信度不转 matched。
拆成独立实施时沿“快照隔离”和“生成校验”两个有界 worker 包执行,不合成通用 schema 平台。
### U6. Backend Intent Admission
目标:修复 F09,落实 R4;依赖 U5 的权威结果结构。
文件:`modules/backend/internal/intentgate/gate.go`、`internal/route/route.go`、`internal/server/server.go`、必要的 `internal/workflow/` 消费点;测试 `internal/intentgate/gate_test.go`、`internal/route/route_test.go`、`internal/server/intent_admission_test.go`(新增)。
方案:传递 missing/invalid 及澄清信息;区分“可开始澄清的流程草稿”和“可执行副作用的完整动作”。命令槽位按已声明 schema 映射,不能无类型地复制所有字符串;已有 text handler 保持兼容。高危动作仍由 cmdbus 确认。
验收:缺槽和非法槽都不会直接执行业务动作;允许澄清的流程可挂起;校验通过后的槽位正确进入 handler;显式取消/纠正尚未实现时诚实回退,不能声明已支持。
### U7. Command Isolation and Receipts
目标:修复 F07 的 Command 部分及 F10,落实 R3/R5/R7;与 U6 可独立。
文件:`deps/agent-command/registry.go`、`result.go`、`conformance/`;`modules/backend/internal/cmdbus/cmdbus.go` 及已有 operation/outbox 的最小存储接口;测试 `deps/agent-command/registry_test.go`、`result_test.go`(不存在时新增)、`modules/backend/internal/cmdbus/cmdbus_test.go`、`account_write_fence_test.go`。
方案:复制 ParamsSchema 与 ExposeAsTool;unknown 允许保留可信 OperationID;handler 区分明确未执行与执行后未知。优先按现有持久化记录提供经 owner/project 授权的查询;若所选命令没有操作记录,则把最小持久化与查询作为该命令适配的组成部分,不能宣布“有 ID 就可查询”。
验收:外部修改返回 spec 不改注册表;成功后 audit 失败返回 unknown 且保留 ID;明确校验拒绝为 rejected;副作用后报错不伪称未执行;未知操作只核对、不自动重放;重复确认/跨用户查询不会重复执行或泄露结果。
跨仓先落兼容库变更,再更新 Backend。完整通用幂等平台不在本批;新幂等键只在选定命令已有明确重复执行风险时设计。
### U8. Squad Durable State
目标:修复 F11,落实 R6/R7;结果解释依赖 U3。
文件:`deps/agent-squad/adapters/sqlite/runstore.go`、迁移入口、`squad/workflow.go` 与现有 checkpoint writer;测试 `adapters/sqlite/runstore_test.go`、`squad/checkpoint_test.go`。
方案:SQLite 完整保存 Mailbox/Handoffs/Board;用明确字段到目标的序列解码,不用内容作 map key。终态保存、manifest 和 checkpoint flush 均检查错误,并在 EvRunCompleted/成功返回之前收敛;不能只在 defer 改 error 而此前已经发出完成事件。保存错误与业务终态分别可见,不能因此重复执行已完成业务动作。
验收:FileStore/SQLite 同一合同 round-trip;恢复后继续收信/移交/认领;相同 clarifications/steering 都保存;sync/async/on_exit 的失败注入均可见;终态保存/manifest 失败不发伪成功事件;旧 schema 升级保留原数据。真实库迁移另行授权。
### U9. Squad Queue Capacity
目标:修复 F12,落实 R6;可独立。
文件:`deps/agent-squad/adapters/sqlite/queue.go`;测试 `adapters/sqlite/queue_test.go`。
方案:一次原子 Claim 同时验证 agent 在途数和 runtime 总在途数,读取/计数错误拒绝认领;保留原租约和重领规则。
验收:runtime cap=1 下两个不同 agent 只能领取一个;同 agent 跨 runtime 仍受 agent cap;并发 Claim 不超限;释放后可继续领取。未接产品的队列模块不因此新接入产品。
### U10. Backend Scoped Runtime Configuration
目标:修复 F13,落实 R2;无需等全部库修复。
文件:`modules/backend/internal/app/app.go`、`internal/runtime/model.go`、`llm.go`、`internal/squadadapter/entry.go`;测试 `internal/app/squad_config_test.go`(新增)、`internal/runtime/model_test.go`、`route_contract_eof_test.go`。
方案:从当前 rc.Settings、run owner、profile 解析完整路由,direct/squad 复用现有配置规则;必要时增加窄的工厂函数。保持项目来源观察/授权限制,不能为接线一致性重新开启被明确禁用的直连工具。
验收:两个用户不同配置与密钥来源不串用;项目覆盖生效;profile model 覆盖使用对应路由规则;MiniMax reasoning 分离和受限 EOF contract 一致;默认配置仍兼容;测试仅用合成密钥和本地 fake server。
### U11. Tool Event Fidelity
目标:修复 F14,落实 R7;跨 Native→Squad→Backend。
文件:`deps/agent-native/runtime/loop.go`、`exec/backend.go`、`adapters/native/backend.go`;`deps/agent-squad/squad/port.go`、`adapters/native/register.go`;`modules/backend/internal/runtime/squad_runner.go`;测试各层现有 host-tools/adapter/runner 测试。
方案:向后兼容新增调用 ID 和真实错误/状态信息,按原语义传递 Text/Detail;宿主事件与最终 message parts 一致。并行回调使用受控串行聚合或局部锁,必要时修复共享 artifactList 的并发访问。
验收:同名工具两次调用各自配对;失败/拒绝/取消不显示 ok;摘要来自实际结果;并行事件/工件无丢失、无 race;旧适配器字段缺省不得被推断为已确认成功。
仅修事件合同,本单元不修改界面布局;若后续需要 UI 展示,先按 DESIGN.md 单列范围。
---
## Functional Follow-Ups
以下是功能建议,不计入前述修复的默认实施范围。推荐顺序为 A1、A2、A3,A4 按真实外部对接需求启动。
| ID | 建议 | 用户收益 | 最小交付与验收 |
|---|---|---|---|
| A1 | 多轮意图闭环 | “把日期改到周五”“取消刚才的任务”能作用于明确对象 | 消费已有 set_slot/change_slot/cancel/expectation;绑定 owner/project/conversation/action/revision;歧义先澄清,过期回答不影响新任务;先覆盖一类真实业务动作 |
| A2 | 执行与恢复诊断 | 看清卡在哪里、能否恢复、是否还要等人 | 在现有 run/operation 查询上提供当前责任角色、等待原因、最后稳定 checkpoint、已知副作用、下一步;先做授权读 API,不先造大型控制台 |
| A3 | 能力目录召回与离线评测 | 不因目录固定 top-8 而遗漏低优先级能力,减少误启动 | 先建立脱敏评测集:否定/引用指令、多意图、错误槽位、过期确认、生成规则一致性;比较准确匹配、误执行、澄清率与成本,再决定是否需要检索或模型调整 |
| A4 | 明确版本的 A2A 互通 | 外部 Agent 任务可查询、可取消并诚实报告状态 | 修 F16;采用官方 fixture/独立服务验证 Card、方法、状态与身份;持久化远端 task ID,处理 auth-required、未知态、超时和取消回执;不自动重发 message/send |
现有 Makefile 已显式覆盖若干 Squad 嵌套 go.mod,但未列 A2A 子模块。
A4 启动时须纳入自己的验证入口,不能假设根 `go test ./...` 自动覆盖独立模块。
不建议本轮新增更多拓扑、动态角色市场、全自动自我改进或向量平台;先用评测证明新能力确有收益。
---
## Verification Contract
实施先用各 F-ID 的具体触发条件形成最小回归,禁止只补“函数能调用”的测试。
不调用真实付费模型,不使用生产数据库,不将已设置的真实凭据输出到日志。
| 验证层 | 范围 | 通过判据 |
|---|---|---|
| 直接回归 | U1-U11 各自文件/包 | 原触发条件被阻断,正常使用不退化 |
| 合同验收 | 现有 conformance 与 adapter 边界 | Native 文本/文件/隔离合同,Squad 成功/失败/未知处理,Intent 规则/生成一致,Command 四态可对账 |
| 定向并发 | 注册表/缓存/队列/事件聚合 | 相关 `-race` 与原子认领用例通过,不机械扩大到所有包 |
| 故障恢复 | 截断 JSONL、存储错误、执行后响应丢失 | 恢复不丢协作状态,不伪称完成,不重复外部副作用 |
| 宿主集成 | 两个用户、项目覆盖、缺槽、确认、direct/squad | 权限与配置作用域一致,确认后只执行一次,结果与事件一致 |
| 组合 | 固定组件及 Backend 嵌套 gitlink | 每批最终候选执行项目规定的 `make test` 和必要构建,按当前门禁保存 SELF;当前 HEAD 必需 CI 为 GATE |
库级测试成功不证明产品装配成功;fake 协议服务成功不证明外部协议兼容;启动 READY 不证明业务功能通过。
对于缺少真实后端的可选路径,清楚标注 NOT_RUN,不阻断已批准且不依赖它的本地修复交付。
## Definition of Done
- 已批准批次的 F-ID 均有对应修复、反例与正常路径验证;被新基线消除的项记录来源,不重复实现。
- 变化的公共合同向后兼容或有明确迁移路径;错误语义和状态持久化可解释。
- 组件 SHA、Backend 嵌套引用与 Dev 组合一致;当前候选通过适用验证、独立 Review 和必需 CI 后按项目规范合入。
- 无未授权部署/真实数据迁移/发布;可选功能保持独立,用户业务验收不被工程自检替代。
## Appendix
### Primary Research
- S1. MCP Tools specification,核对版本 `2025-11-25`,研究日 2026-10-21:工具定义、结构化输入/输出与 annotations 信任边界。`https://modelcontextprotocol.io/specification/2025-11-25/server/tools`。影响 KTD2;未要求升级协议或引入 SDK。
- S2. LangGraph Persistence 官方文档,研究日 2026-10-21:checkpointer 的线程状态与 store 的跨线程数据各有作用域。`https://docs.langchain.com/oss/python/langgraph/persistence`。影响 KTD5;仅借鉴状态边界,不引入 LangGraph。
- S3. A2A `v1.0.1` 官方 specification 与 v1 变更说明,研究日 2026-10-21:协议方法、Agent Card 的 supportedInterfaces 和任务状态表示。`https://a2a-protocol.org/v1.0.1/specification/`、`https://a2a-protocol.org/v1.0.1/whats-new-v1/`。影响 F16/KTD8/A4;不混用开发中 PR 版本。
外部研究用于校验契约和改进方案,不能单凭行业实践宣判本项目缺陷。
进一步获取 Go os/exec 与 LangGraph interrupts 网页时检索工具连接失败;本方案未把未获取内容列为外部证据。
### Remaining Decisions
建议批准第一批 U1-U7、U10 后连续实施到各自可验收的集成结果,再进入第二批 U8、U9、U11。
也可一次批准两批,但不因此纳入 A1-A4。
需要用户确定的是实施批次与后续功能优先级;具体函数、补充字段和回归组织由实施者在上述合同内确定,不再逐文件请求许可。
当前没有证据支持宣称线上已被利用;部署状态、真实模型行为和外部 A2A 互通仍待相应阶段验证。
+6
View File
@@ -189,6 +189,12 @@ scripts/svc.sh <name> status # 单服务详情
- 显式给任何运行参数(或 `--config` 文件、`SVC_*` 变量)时**整体重建**参数;
- agent/static 换端口:`restart --port N`(进程与登记同步更新);
- postgres 换端口:`set --apply`(见 §7.4)或 `restart --port N` 同样有效。
- **超级管理员凭据文件(agent,`superadmin_envfile=`)**:在 deploy/start/restart 时传一次
`AGENT_SYSTEM_ENV_FILE=<文件>`,svc 先校验(普通文件、属当前用户、权限 0600 即无组/其他位,
不合格则在停旧服务前拒绝),再只把**绝对路径**记入 svc.env(从不读取、复制或打印文件内容);
此后裸 `restart`、`recover`/watchdog 拉起都复用该路径。本次环境显式设置的
`SUPER_ADMIN_OWNER`/`SUPER_ADMIN_PASSWORD`/`AGENT_SYSTEM_ENV_FILE` 优先于登记;
`AGENT_SYSTEM_ENV_FILE=`(空值)清除登记;登记文件已不存在时告警且不转发(serve 按缺凭据拒启)。
`status` 输出示例(postgres):
+93 -3
View File
@@ -942,6 +942,7 @@ svc_merge_tags() {
# (托管绑定不重复存 DSN,外部直连存受保护 dsn=);未走解析的调用保持旧值。
record_state() {
local pid prev_src prev_dbref prev_conffile prev_db prev_dsn prev_envfile prev_plugins plugins_out merged_tags
local prev_saenv
pid="$(read_pid)"
prev_src=""
[ -f "$ENV_DIR/svc.env" ] && prev_src="$(svc_get source)"
@@ -950,6 +951,7 @@ record_state() {
prev_db="$(svc_get db)"
prev_dsn="$(svc_get dsn)"
prev_envfile="$(svc_get envfile)"
prev_saenv="$(svc_get superadmin_envfile)"
prev_plugins="$(svc_get plugins)"
plugins_out="$prev_plugins"
# 只有激活校验和 UI 探活都通过之后才提交新的 plugins=。中途 record_state
@@ -999,6 +1001,11 @@ record_state() {
elif [ -n "$prev_envfile" ]; then
printf 'envfile=%s\n' "$prev_envfile"
fi
if [ -n "${SUPERADMIN_ENVFILE_NEW:-}" ]; then
printf 'superadmin_envfile=%s\n' "$SUPERADMIN_ENVFILE_NEW"
elif [ "${SUPERADMIN_ENVFILE_CLEAR:-0}" != 1 ] && [ -n "$prev_saenv" ]; then
printf 'superadmin_envfile=%s\n' "$prev_saenv"
fi
[ -n "$plugins_out" ] && printf 'plugins=%s\n' "$plugins_out"
[ $# -gt 0 ] && printf '%s\n' "$@"
} > "$tmp"
@@ -1355,6 +1362,8 @@ svc_identity_run() {
svc_db_identity_ensure() {
# 仅 fresh/pending 或显式 adopt 调用。established 缺行不得走这里自动补。
# 非 agent 或无能力:跳过。可选参数 BIN:部署候选。
# SVC_IDENTITY_LAST_OUT:最近一次 identity CLI 输出,供候选门禁区分失败原因。
SVC_IDENTITY_LAST_OUT=""
local bin="${1:-${ENV_BIN:-}}"
manifest_ok || return 0
local kind; kind="$(svc_get kind)"
@@ -1365,6 +1374,7 @@ svc_db_identity_ensure() {
IFS=$'\t' read -r iid dbref data <<<"$line"
local out rc=0
out="$(svc_identity_run "$bin" ensure "$iid" "$dbref" "$data" 2>&1)" || rc=$?
SVC_IDENTITY_LAST_OUT="$out"
if [ "$rc" -ne 0 ]; then
c_err "[$ENV_NAME] DB 身份 ensure 失败:$out"
return 1
@@ -1374,6 +1384,7 @@ svc_db_identity_ensure() {
}
svc_db_identity_verify() {
SVC_IDENTITY_LAST_OUT=""
manifest_ok || return 0
local kind; kind="$(svc_get kind)"
[ "$kind" = agent ] || return 0
@@ -1384,6 +1395,7 @@ svc_db_identity_verify() {
IFS=$'\t' read -r iid dbref data <<<"$line"
local out rc=0
out="$(svc_identity_run "$bin" verify "$iid" "$dbref" "$data" 2>&1)" || rc=$?
SVC_IDENTITY_LAST_OUT="$out"
if [ "$rc" -ne 0 ]; then
c_err "[$ENV_NAME] DB 身份校验失败,拒绝启动:$out"
c_err " 先调查库文件与身份行;确认归属后 scripts/svc.sh $ENV_NAME adopt --confirm $iid"
@@ -1395,6 +1407,22 @@ svc_db_identity_verify() {
return 0
}
# svc_identity_schema_missing SRCBIN:最近一次 identity CLI 报身份表缺失(库 schema
# 早于 v27)时打印迁移指引并返回 0;否则返回 1,调用方沿用原失败提示。不自动迁移。
svc_identity_schema_missing() {
case "${SVC_IDENTITY_LAST_OUT:-}" in
*"identity table missing"*) ;;
*) return 1 ;;
esac
local data_root
data_root="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["identity"]["data_root"])' "$ENV_DIR/manifest.json" 2>/dev/null)" \
|| data_root="<data_root>"
c_err "[$ENV_NAME] 数据库 schema 早于身份表(pre-v27,identity table missing):候选无法补齐/验证 DB 身份;旧服务/旧制品/登记保持不变"
c_err " 处理:先备份数据库 → scripts/svc.sh $ENV_NAME stop → 用候选制品执行 $1 migrate --data $data_root(PostgreSQL:DSN 只经环境变量 AGENT_SYSTEM_DSN 传入,不进 argv)→ 再次部署"
c_err " svc 不会自动迁移;本次暂存的候选在拒绝后清理,迁移请使用同一版本的制品"
return 0
}
# svc_deploy_identity_gate SRCBIN:部署候选的身份门禁——在停旧服务、覆盖制品之前
# 执行(2026-10-12 rdm1/rdmcand 事故:停服→覆盖→启动即被身份门禁拒绝,只剩
# 「登记版本≠磁盘制品」的混合状态且无恢复路径)。规则:
@@ -1425,7 +1453,8 @@ svc_deploy_identity_gate() {
data_root="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["identity"]["data_root"])' "$ENV_DIR/manifest.json")"
if [ -f "$data_root/agent-system.db" ] || { [ "${DB_MODE:-}" = pg ] && [ -n "${DB_DSN:-}" ]; }; then
svc_db_identity_ensure "$srcbin" || {
c_err "[$ENV_NAME] 候选补齐 DB 身份失败(外宾行/不一致 fail-closed);旧服务/旧制品/登记保持不变"
svc_identity_schema_missing "$srcbin" \
|| c_err "[$ENV_NAME] 候选补齐 DB 身份失败(外宾行/不一致 fail-closed);旧服务/旧制品/登记保持不变"
return 1
}
fi
@@ -1433,7 +1462,8 @@ svc_deploy_identity_gate() {
*)
# established:只验证,不补行。缺行或不匹配在停旧服务前拒绝。
svc_db_identity_verify "$srcbin" || {
c_err "[$ENV_NAME] 候选无法验证已建立的 DB 身份;旧服务/旧制品/登记保持不变"
svc_identity_schema_missing "$srcbin" \
|| c_err "[$ENV_NAME] 候选无法验证已建立的 DB 身份;旧服务/旧制品/登记保持不变"
return 1
}
;;
@@ -1506,6 +1536,38 @@ svc_write_serve_env() {
chmod 0600 "$f" 2>/dev/null || true
}
# svc_superadmin_envfile_check:agent 的 start/restart/deploy 在停旧服务前校验本次
# AGENT_SYSTEM_ENV_FILE(只看路径与元数据,绝不读内容)。非空须为当前用户所有、
# 无组/其他权限位的普通文件,通过后由 record_state 记为 superadmin_envfile=;
# 已设置但为空则清除登记。
SUPERADMIN_ENVFILE_NEW=""
SUPERADMIN_ENVFILE_CLEAR=0
svc_superadmin_envfile_check() {
[ -n "${AGENT_SYSTEM_ENV_FILE+x}" ] || return 0
if [ -z "$AGENT_SYSTEM_ENV_FILE" ]; then
SUPERADMIN_ENVFILE_CLEAR=1
return 0
fi
local f="$AGENT_SYSTEM_ENV_FILE" dir meta owner mode
dir="$(cd "$(dirname "$f")" 2>/dev/null && pwd)" || dir=""
[ -n "$dir" ] && f="${dir%/}/$(basename "$f")"
if [ -z "$dir" ] || [ ! -f "$f" ]; then
c_err "[$ENV_NAME] 超级管理员凭据文件不存在或不是普通文件:$f(运行中的服务未改动)"
return 1
fi
meta="$(stat -L -c '%u %a' "$f" 2>/dev/null)" || meta=""
owner="${meta%% *}"; mode="${meta##* }"
if [ -z "$meta" ] || [ "$owner" != "$(id -u)" ]; then
c_err "[$ENV_NAME] 超级管理员凭据文件不属于当前用户:$f(运行中的服务未改动)"
return 1
fi
if [ $(( 8#$mode & 8#077 )) -ne 0 ]; then
c_err "[$ENV_NAME] 超级管理员凭据文件权限过宽($mode):$f,须 chmod 600(运行中的服务未改动)"
return 1
fi
SUPERADMIN_ENVFILE_NEW="$f"
}
# svc_launch_serve ARGS:env -i 只带 serve.env 与本次构造的 guard_env。
svc_launch_serve() {
local exec_args="$1"
@@ -1535,11 +1597,25 @@ svc_launch_serve() {
args+=("RDM_DISABLE_EXEC_TOOLS=$RDM_DISABLE_EXEC_TOOLS")
fi
# 超级管理员凭据只从本次环境转发,不写入 serve.env;缺失时由 serve 自行 fail closed。
# 本次环境三者皆未设置时回落到 svc.env 登记的 superadmin_envfile=(只转发路径),
# recover/watchdog 的清空环境子进程因此也能拉起认证实例。
local sa_explicit=0 sa_file
for k in SUPER_ADMIN_OWNER SUPER_ADMIN_PASSWORD AGENT_SYSTEM_ENV_FILE; do
if [ -n "${!k+x}" ]; then
args+=("$k=${!k}")
sa_explicit=1
fi
done
if [ "$sa_explicit" = 0 ]; then
sa_file="$(svc_get superadmin_envfile)"
if [ -n "$sa_file" ]; then
if [ -f "$sa_file" ]; then
args+=("AGENT_SYSTEM_ENV_FILE=$sa_file")
else
c_warn "[$ENV_NAME] 登记的超级管理员凭据文件已不存在:$sa_file(不转发,serve 将按缺凭据拒启)"
fi
fi
fi
# shellcheck disable=SC2086
nohup "${args[@]}" "$ENV_BIN" serve $exec_args >> "$ENV_DIR/svc.log" 2>&1 9>&- &
}
@@ -5553,6 +5629,8 @@ env_status_detail() {
cfile="$(svc_get conffile)"
[ -z "$cfile" ] && cfile="$(printf '%s' "$(svc_get exec)" | sed -n 's/.*--config \([^ ]*\).*/\1/p')"
printf ' 配置文件 %s\n' "${cfile:-无(命令行参数形态;<name> config <file> 绑定)}"
local saf; saf="$(svc_get superadmin_envfile)"
[ -n "$saf" ] && printf ' 超管凭据文件 %s(仅登记路径)\n' "$saf"
printf ' 监听守卫 insecureListen=%s allowedHosts=%s\n' \
"$(env_var "$ENV_NAME" INSECURE_LISTEN)" \
"$(env_var "$ENV_NAME" ALLOWED_HOSTS)"
@@ -6429,6 +6507,12 @@ main() {
exit 2
fi
env_config "$ENV_NAME"
case "$action" in
start|restart|deploy)
if [ "$SVC_KIND" = agent ]; then
svc_superadmin_envfile_check || exit 1
fi ;;
esac
# 生命周期动作持 .start.lock(wait 模式;进程退出自动释放)——deploy 全程含
# 构建窗口,防 recover/并发 start 在停机-换产物间隙抢跑。只读动作不加锁。
case "$action" in
@@ -6507,7 +6591,13 @@ finally:
PY
svc_db_marker_set pending || exit 1
svc_db_identity_ensure || { c_err "[$ENV_NAME] adopt ensure 失败"; exit 1; }
c_ok "[$ENV_NAME] adopt 完成,db_marker=established" ;;
local adopt_marker; adopt_marker="$(svc_db_marker)"
if [ "$adopt_marker" = established ]; then
c_ok "[$ENV_NAME] adopt 完成,db_marker=established"
else
c_warn "[$ENV_NAME] adopt 未建立 DB 身份:db_marker=$adopt_marker(当前已装制品不具备 identity 能力,无法建立身份)"
c_warn " 部署具备 identity ensure/verify 的制品后,由候选身份门禁补齐"
fi ;;
status) env_status_detail ;;
logs) [ -f "$ENV_LOG" ] && tail -n "${CLI_LOGS_N:-50}" -f "$ENV_LOG" || c_warn "[$ENV_NAME] 暂无日志" ;;
esac
+9 -4
View File
@@ -1,6 +1,11 @@
# RDM 制品钉扎。三行都是 40 位 hex。升级走 PR,不在 CI 里执行候选文件。
# legacy 没有 version --json。prev 是身份能力合入(#291)。sha 与 prev 钉同一
# backend(0f192a4),schema 兼容,但产品树不同,切换能在 release 名上看到。
sha=c6304224f995747ea3ffcb8d0e81f3ad46a96546
prev_sha=3484ec225b0087a9065466ed436ea97a768691ef
# sha:生命周期套件的目标版本(6-pin 布局)。
# prev_sha:来回部署身份稳定性测试的起点。选取受两条硬约束——与 sha 的 pins 布局一致,
# 且两者之间无跨 schema 迁移(scripts/svc.sh 的 schema 兼容闸会在停旧服务前拒绝不兼容切换)——
# 而非追求版本跨度。取 sha 在 RDM 主线上的第一父提交(交付合入前的 main);代价是 prev 与 sha
# 仅隔一次合并,版本跨度小,往返部署覆盖的版本距离有限。
# legacy_sha:承载「无身份」语义(没有 version --json),必然早于 6-pin 布局。
# 三者的 pins 布局可能不同;CI 按每个 pin 自身的 gitlink 清单物化并逐个断言,不要求布局统一。
sha=bc8db886606e88201e7f7210a1357d22dc84124b
prev_sha=59b3624325fe132ef92ccee83dd9ddc6c3fcc521
legacy_sha=72bb1fec79d64c2af5e49416640712320816c3b4
@@ -153,7 +153,7 @@ func TestTwoInstancesOverSharedPG(t *testing.T) {
t.Fatalf("stale event replayed: seq %d <= since %d", ev.seq, lastSeq)
}
case <-time.After(8 * time.Second):
t.Fatal("event written via A not visible on B SSE within 8s")
t.Fatalf("event written via A not visible on B SSE within 8s: no event with seq > %d observed on %s", lastSeq, e.instanceOf(uc.streamB))
}
})
@@ -212,17 +212,24 @@ func TestTwoInstancesOverSharedPG(t *testing.T) {
// (interrupted),随后原实例重新占用会话并推进。
e.a = e.startInstance(t, e.a.name, strings.TrimPrefix(e.a.base, "http://"))
var detail map[string]any
deadline := time.Now().Add(httpWait)
var statuses []string
reconciled := false
start := time.Now()
deadline := start.Add(httpWait)
for time.Now().Before(deadline) {
e.do(t, uc.api, "GET", "/api/conversations/"+convID, nil, &detail)
if runs, ok := detail["runs"].([]any); ok && len(runs) > 0 {
last, _ := runs[len(runs)-1].(map[string]any)
if last["status"] == "interrupted" {
break
}
statuses = runStatuses(detail)
if n := len(statuses); n > 0 && statuses[n-1] == "interrupted" {
reconciled = true
break
}
time.Sleep(300 * time.Millisecond)
}
if !reconciled {
// 不能落到下面的 POST:未收口时它会得到看似无关的 409 in-flight。
t.Fatalf("restarted %s never reported the crashed run as interrupted: waited %v (budget %v), last run status never became \"interrupted\"; run statuses %v; log:\n%s",
e.instanceOf(uc.api), time.Since(start).Round(time.Millisecond), httpWait, statuses, e.a.log.String())
}
e.do(t, uc.api, "POST", "/api/conversations/"+convID+"/messages", map[string]any{"text": "A resumes after restart"}, nil)
})
@@ -230,6 +237,36 @@ func TestTwoInstancesOverSharedPG(t *testing.T) {
e.killNine(t, e.b)
e.b = e.startInstance(t, e.b.name, strings.TrimPrefix(e.b.base, "http://"))
// 断言前先等重启实例给出完整的恢复视图:对账收口的 interrupted run 可见,
// 且会话内无进行中 run。断言本身不依赖这次轮询的结果。
var statuses []string
recovered := false
start := time.Now()
readyBy := start.Add(httpWait)
for time.Now().Before(readyBy) {
var view map[string]any
e.do(t, uc.apiB, "GET", "/api/conversations/"+convID, nil, &view)
statuses = runStatuses(view)
interrupted, busy := false, false
for _, s := range statuses {
switch s {
case "interrupted":
interrupted = true
case "pending", "running", "waiting_hitl":
busy = true
}
}
if interrupted && !busy {
recovered = true
break
}
time.Sleep(300 * time.Millisecond)
}
if !recovered {
t.Fatalf("restarted %s never showed the recovered view (an interrupted run and no pending/running/waiting_hitl run): waited %v (budget %v); run statuses %v; logA:\n%s\nlogB:\n%s",
e.instanceOf(uc.apiB), time.Since(start).Round(time.Millisecond), httpWait, statuses, e.a.log.String(), e.b.log.String())
}
var detail map[string]any
e.do(t, uc.apiB, "GET", "/api/conversations/"+convID, nil, &detail)
msgs, _ := detail["messages"].([]any)
@@ -255,7 +292,7 @@ func TestTwoInstancesOverSharedPG(t *testing.T) {
}
seen = ev.seq
case <-deadline:
t.Fatalf("replay stalled at seq %d / %d", seen, lastSeq)
t.Fatalf("replay stalled at seq %d / %d: event seq %d never observed on %s SSE within 15s", seen, lastSeq, seen+1, e.instanceOf(uc.streamB))
}
}
// 重启后写入仍可推进。
@@ -410,6 +447,7 @@ func (e *env) uploadArtifact(t *testing.T, c *http.Client, projectID, name strin
func (e *env) waitHealthy(t *testing.T, inst *instance) {
t.Helper()
last := "no probe completed"
deadline := time.Now().Add(60 * time.Second)
for time.Now().Before(deadline) {
resp, err := http.Get(inst.base + "/api/health")
@@ -419,10 +457,13 @@ func (e *env) waitHealthy(t *testing.T, inst *instance) {
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusUnauthorized {
return
}
last = "status " + strconv.Itoa(resp.StatusCode)
} else {
last = err.Error()
}
time.Sleep(300 * time.Millisecond)
}
t.Fatalf("instance %s not healthy in 60s; log:\n%s", inst.name, inst.log.String())
t.Fatalf("instance %s (%s) not healthy in 60s: GET /api/health never returned 200/401 (last probe: %s); log:\n%s", inst.name, inst.base, last, inst.log.String())
}
func (e *env) stopInstance(inst *instance) {
@@ -642,6 +683,27 @@ func (e *env) baseFor(c *http.Client) string {
return e.a.base
}
// instanceOf 给出 client 实际轮询的实例(名 + 地址),供有界等待的失败信息定位。
func (e *env) instanceOf(c *http.Client) string {
base := e.baseFor(c)
if e.b != nil && base == e.b.base {
return e.b.name + " (" + base + ")"
}
return e.a.name + " (" + base + ")"
}
// runStatuses 按顺序取会话详情里各 run 的状态,供等待条件与失败信息复用。
func runStatuses(detail map[string]any) []string {
runs, _ := detail["runs"].([]any)
out := make([]string, 0, len(runs))
for _, r := range runs {
m, _ := r.(map[string]any)
s, _ := m["status"].(string)
out = append(out, s)
}
return out
}
// ---- 场景辅助 ----
type sseEvent struct {
@@ -671,10 +733,13 @@ func (e *env) postMessage(t *testing.T, c *http.Client, convID, text string) {
func (e *env) waitConversationIdle(t *testing.T, c *http.Client, convID string, within time.Duration) {
t.Helper()
deadline := time.Now().Add(within)
var statuses []string
start := time.Now()
deadline := start.Add(within)
for time.Now().Before(deadline) {
var detail map[string]any
e.do(t, c, "GET", "/api/conversations/"+convID, nil, &detail)
statuses = runStatuses(detail)
busy := false
if runs, ok := detail["runs"].([]any); ok {
for _, r := range runs {
@@ -690,7 +755,8 @@ func (e *env) waitConversationIdle(t *testing.T, c *http.Client, convID string,
}
time.Sleep(300 * time.Millisecond)
}
t.Fatalf("conversation %s still busy after %v", convID, within)
t.Fatalf("conversation %s still busy on %s: waited %v (budget %v) and never observed all runs out of pending/running/waiting_hitl; run statuses %v",
convID, e.instanceOf(c), time.Since(start).Round(time.Millisecond), within, statuses)
}
func (e *env) lastSeq(t *testing.T, c *http.Client, convID string) int64 {
@@ -755,11 +821,17 @@ func (e *env) subscribe(t *testing.T, c *http.Client, convID string, since int64
func (e *env) waitActiveHITL(t *testing.T, c *http.Client, convID string) hitlCard {
t.Helper()
deadline := time.Now().Add(15 * time.Second)
var statuses []string
cardCount := 0
start := time.Now()
deadline := start.Add(15 * time.Second)
for time.Now().Before(deadline) {
var detail map[string]any
e.do(t, c, "GET", "/api/conversations/"+convID, nil, &detail)
if cards, ok := detail["activeHitl"].([]any); ok && len(cards) > 0 {
statuses = runStatuses(detail)
cards, ok := detail["activeHitl"].([]any)
cardCount = len(cards)
if ok && len(cards) > 0 {
m, _ := cards[0].(map[string]any)
id, _ := m["id"].(string)
runID, _ := m["runId"].(string)
@@ -769,21 +841,27 @@ func (e *env) waitActiveHITL(t *testing.T, c *http.Client, convID string) hitlCa
}
time.Sleep(300 * time.Millisecond)
}
t.Fatalf("no active HITL card in 15s; logA:\n%s", e.a.log.String())
t.Fatalf("no active HITL card with id and runId on %s for conversation %s: waited %v (budget 15s); last activeHitl count %d, run statuses %v; logA:\n%s",
e.instanceOf(c), convID, time.Since(start).Round(time.Millisecond), cardCount, statuses, e.a.log.String())
return hitlCard{}
}
func (e *env) waitRunStatus(t *testing.T, c *http.Client, runID, status string) {
t.Helper()
deadline := time.Now().Add(httpWait)
observed := "<none>"
start := time.Now()
deadline := start.Add(httpWait)
for time.Now().Before(deadline) {
var out map[string]any
e.do(t, c, "GET", "/api/runs/"+runID, nil, &out)
run, _ := out["run"].(map[string]any)
if s, _ := run["status"].(string); s == status {
s, _ := run["status"].(string)
if s == status {
return
}
observed = s
time.Sleep(300 * time.Millisecond)
}
t.Fatalf("run %s not %s within %v", runID, status, httpWait)
t.Fatalf("run %s not %s on %s: waited %v (budget %v), last observed status %q; logA:\n%s",
runID, status, e.instanceOf(c), time.Since(start).Round(time.Millisecond), httpWait, observed, e.a.log.String())
}
+45
View File
@@ -413,6 +413,51 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
hang.chmod(0o755)
self.assertIn("rc=1", probe(hang), "探测挂死须有界返回无能力")
def test_U1_adopt_identityless_artifact_reports_pending(self):
"""已装制品无 identity 能力:adopt 的 ensure 静默跳过,marker 留在 pending——
输出必须如实报 pending,不得宣称 db_marker=established。"""
self.seed_agent("adx", 9454)
self.start_ok("adx")
iid = self.manifest("adx")["instance_id"]
self.svc("adx", "stop")
self.install_noid_bin("adx")
result = self.svc("adx", "adopt", "--confirm", iid)
self.assertNotIn("db_marker=established", result.stdout)
self.assertIn("db_marker=pending", result.stdout)
self.assertEqual(self.manifest("adx")["identity"]["db_marker"], "pending")
def test_U1_update_pre_v27_schema_refused_with_migrate_guidance(self):
"""候选有 identity 能力但库 schema 早于身份表:停服前拒绝,给出
备份→stop→migrate→再部署的指引,而非「外宾行」误导;不自动迁移。"""
cand = self.root / "stub-agent-system-noschema"
cand.write_text(
'#!/usr/bin/env bash\ncase "${1:-}" in identity) '
'echo "identity ${2:-ensure}: identity table missing" >&2; exit 1 ;; '
'*) exec "%s" "$@" ;; esac\n' % self.stub)
cand.chmod(0o755)
remote, old_sha, _ = self._make_track_repo(cand, "agent-system")
self.git_ok(self.root, "clone", str(remote), str(self.root / "srccache"))
self.seed_agent("scx", 9455)
self.start_ok("scx")
self.set_kv("scx", "source", "dev@%s" % old_sha)
mpath = self.runtime("scx") / "manifest.json"
m = json.loads(mpath.read_text(encoding="utf-8"))
m["identity"]["db_marker"] = "pending"
mpath.write_text(json.dumps(m, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
pid = self.pid_of("scx")
bin_before = (self.runtime("scx") / "bin" / "agent-system").read_bytes()
result = self.svc("update", "scx", check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("migrate --data", result.stdout)
self.assertIn("AGENT_SYSTEM_DSN", result.stdout)
self.assertNotIn("外宾行", result.stdout)
self.assertTrue(self.running("scx"), "拒绝必须发生在停旧服务之前")
self.assertEqual(self.pid_of("scx"), pid)
self.assertEqual((self.runtime("scx") / "bin" / "agent-system").read_bytes(),
bin_before, "旧制品不得被覆盖")
self.svc("scx", "stop")
def test_I3_start_ensures_sqlite_identity(self):
self.seed_agent("idb1", 9441)
self.start_ok("idb1")
+194
View File
@@ -0,0 +1,194 @@
"""svc.sh 超级管理员凭据文件路径登记回归(superadmin_envfile=)。
deploy/start/restart 时传一次 AGENT_SYSTEM_ENV_FILE:svc 校验(普通文件、属当前用户、
无组/其他权限位)后只登记路径;裸 restart 与 recover(清空环境的生命周期子进程)
复用该路径转发给 serve;权限过宽在停旧服务前拒绝;空值清除登记;svc.env/svc.log
绝不出现文件内容。桩二进制与夹具复用 tests/test_svc_recover.py。
"""
import importlib.util
import os
import shutil
import unittest
from pathlib import Path
SOURCE = Path(__file__).resolve().parents[1]
_spec = importlib.util.spec_from_file_location("tsr", SOURCE / "tests" / "test_svc_recover.py")
tsr = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(tsr)
# 桩 serve 额外记录实际收到的 AGENT_SYSTEM_ENV_FILE(未设置记 <unset>)。
_ANCHOR = '> "$trace_dir/serve.txt"\n'
assert _ANCHOR in tsr.STUB
STUB = tsr.STUB.replace(
_ANCHOR,
_ANCHOR + r""" printf 'AGENT_SYSTEM_ENV_FILE=%s\n' "${AGENT_SYSTEM_ENV_FILE-<unset>}" >> "$trace_dir/serve.txt"
""", 1)
PASSWORD = "Dummy-SA-Pw-9f4c2e71-do-not-leak"
UNSET = "<unset>"
class SuperAdminEnvfileTests(tsr.SvcAutoRecoverTests):
def setUp(self):
super().setUp()
shutil.copy2(SOURCE / "scripts" / "svc_manifest.py",
self.root / "scripts" / "svc_manifest.py")
self.stub.write_text(STUB)
for key in ("AGENT_SYSTEM_ENV_FILE", "SUPER_ADMIN_OWNER", "SUPER_ADMIN_PASSWORD"):
self.env.pop(key, None)
self.secrets = self.root / "secrets"
self.secrets.mkdir(mode=0o700)
self.cred = self.make_cred("sa.env", 0o600)
# ---- 夹具 ----
def make_cred(self, name, mode):
p = self.secrets / name
p.write_text("SUPER_ADMIN_OWNER=op\nSUPER_ADMIN_PASSWORD=%s\n" % PASSWORD)
p.chmod(mode)
return p
def env_with(self, **extra):
env = dict(self.env)
env.update(extra)
return env
def served_envfile(self):
for line in (self.trace / "serve.txt").read_text().splitlines():
if line.startswith("AGENT_SYSTEM_ENV_FILE="):
return line.split("=", 1)[1]
return None
def fresh_trace(self):
f = self.trace / "serve.txt"
if f.exists():
f.unlink()
def assert_same_path(self, recorded, expected):
self.assertIsNotNone(recorded)
self.assertTrue(os.path.isabs(recorded), recorded)
self.assertEqual(os.path.realpath(recorded), os.path.realpath(expected))
def assert_no_secret(self, name, *outputs):
for fname in ("svc.env", "svc.log", "manifest.json", "serve.env", "history.jsonl"):
p = self.runtime(name) / fname
if p.exists():
self.assertNotIn(PASSWORD, p.read_text(errors="replace"), fname)
for out in outputs:
self.assertNotIn(PASSWORD, out)
def start_with_cred(self, name, cred=None):
result = self.svc(name, "start",
env=self.env_with(AGENT_SYSTEM_ENV_FILE=str(cred or self.cred)))
self.assertIn("已启动", result.stdout)
return result
# ---- 用例 ----
def test_sa_start_records_path_and_bare_restart_reuses_it(self):
self.seed_agent("dev", tsr.free_fixture_port())
r1 = self.start_with_cred("dev")
self.assert_same_path(self.kv("dev", "superadmin_envfile"), self.cred)
self.assert_same_path(self.served_envfile(), self.cred)
status = self.svc("dev", "status")
self.assertIn("超管凭据文件", status.stdout)
self.fresh_trace()
r2 = self.svc("dev", "restart") # 环境里不再有 AGENT_SYSTEM_ENV_FILE
self.assertIn("已启动", r2.stdout)
self.assert_same_path(self.served_envfile(), self.cred)
self.assert_same_path(self.kv("dev", "superadmin_envfile"), self.cred)
self.assert_no_secret("dev", r1.stdout, r2.stdout, status.stdout)
def test_sa_relative_path_recorded_absolute(self):
self.seed_agent("dev", tsr.free_fixture_port())
result = self.svc("dev", "start",
env=self.env_with(AGENT_SYSTEM_ENV_FILE="secrets/sa.env"))
self.assertIn("已启动", result.stdout)
self.assert_same_path(self.kv("dev", "superadmin_envfile"), self.cred)
def test_sa_recover_forwards_recorded_path(self):
self.seed_agent("dev", tsr.free_fixture_port())
self.start_with_cred("dev")
old_pid = self.kill9("dev")
self.fresh_trace()
result = self.svc("recover", "--quiet")
self.assertIn("recover 汇总:拉回 1", result.stdout)
new_pid = self.pid_of("dev")
self.assertTrue(self.pid_alive(new_pid))
self.assertNotEqual(new_pid, old_pid)
self.assert_same_path(self.served_envfile(), self.cred)
self.assert_same_path(self.kv("dev", "superadmin_envfile"), self.cred)
self.assert_no_secret("dev", result.stdout)
def test_sa_loose_permissions_refused_before_stop(self):
self.seed_agent("dev", tsr.free_fixture_port())
self.start_ok("dev")
pid = self.pid_of("dev")
before = (self.runtime("dev") / "svc.env").read_bytes()
loose = self.make_cred("loose.env", 0o644)
result = self.svc("dev", "restart", check=False,
env=self.env_with(AGENT_SYSTEM_ENV_FILE=str(loose)))
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("权限过宽", result.stdout)
self.assertIn(str(loose), result.stdout)
self.assertEqual(self.pid_of("dev"), pid)
self.assertTrue(self.pid_alive(pid), "拒绝时不得停掉运行中的服务")
self.assertEqual((self.runtime("dev") / "svc.env").read_bytes(), before)
self.assert_no_secret("dev", result.stdout)
def test_sa_missing_file_refused_before_stop(self):
self.seed_agent("dev", tsr.free_fixture_port())
self.start_ok("dev")
pid = self.pid_of("dev")
result = self.svc("dev", "restart", check=False,
env=self.env_with(AGENT_SYSTEM_ENV_FILE=str(self.secrets / "nope.env")))
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("nope.env", result.stdout)
self.assertTrue(self.pid_alive(pid))
self.assertEqual(self.pid_of("dev"), pid)
def test_sa_empty_value_clears_key(self):
self.seed_agent("dev", tsr.free_fixture_port())
self.start_with_cred("dev")
self.assertIsNotNone(self.kv("dev", "superadmin_envfile"))
self.fresh_trace()
self.svc("dev", "restart", env=self.env_with(AGENT_SYSTEM_ENV_FILE=""))
self.assertIsNone(self.kv("dev", "superadmin_envfile"))
self.assertEqual(self.served_envfile(), "")
self.fresh_trace()
self.svc("dev", "restart")
self.assertEqual(self.served_envfile(), UNSET)
def test_sa_explicit_environment_overrides_record(self):
self.seed_agent("dev", tsr.free_fixture_port())
self.start_with_cred("dev")
self.fresh_trace()
r = self.svc("dev", "restart", env=self.env_with(SUPER_ADMIN_OWNER="op"))
self.assertEqual(self.served_envfile(), UNSET, "显式凭据环境优先,不回落登记路径")
self.assert_same_path(self.kv("dev", "superadmin_envfile"), self.cred)
other = self.make_cred("other.env", 0o600)
self.fresh_trace()
self.svc("dev", "restart", env=self.env_with(AGENT_SYSTEM_ENV_FILE=str(other)))
self.assert_same_path(self.served_envfile(), other)
self.assert_same_path(self.kv("dev", "superadmin_envfile"), other)
self.assert_no_secret("dev", r.stdout)
def test_sa_recorded_file_gone_warns_and_skips(self):
self.seed_agent("dev", tsr.free_fixture_port())
self.start_with_cred("dev")
self.cred.unlink()
self.fresh_trace()
result = self.svc("dev", "restart")
self.assertIn("已不存在", result.stdout)
self.assertEqual(self.served_envfile(), UNSET)
# 只跑本文件用例;继承来的自恢复用例由 tests/test_svc_recover.py 负责。
for _name in dir(tsr.SvcAutoRecoverTests):
if _name.startswith("test_") and _name not in SuperAdminEnvfileTests.__dict__:
setattr(SuperAdminEnvfileTests, _name, None)
if __name__ == "__main__":
unittest.main()