Merge pull request 'Report the real identity marker in svc adopt and the deploy gate' (#681) from fix/genui-prod-upgrade-followups into main
ci / svc-realcombo (push) Successful in 4m5s
ci / test (push) Successful in 8m35s
ci / test-pg (push) Successful in 13m30s

This commit was merged in pull request #681.
This commit is contained in:
2026-10-21 14:00:56 +00:00
4 changed files with 211 additions and 4 deletions
@@ -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 |
+32 -3
View File
@@ -1355,6 +1355,8 @@ svc_identity_run() {
svc_db_identity_ensure() { svc_db_identity_ensure() {
# 仅 fresh/pending 或显式 adopt 调用。established 缺行不得走这里自动补。 # 仅 fresh/pending 或显式 adopt 调用。established 缺行不得走这里自动补。
# 非 agent 或无能力:跳过。可选参数 BIN:部署候选。 # 非 agent 或无能力:跳过。可选参数 BIN:部署候选。
# SVC_IDENTITY_LAST_OUT:最近一次 identity CLI 输出,供候选门禁区分失败原因。
SVC_IDENTITY_LAST_OUT=""
local bin="${1:-${ENV_BIN:-}}" local bin="${1:-${ENV_BIN:-}}"
manifest_ok || return 0 manifest_ok || return 0
local kind; kind="$(svc_get kind)" local kind; kind="$(svc_get kind)"
@@ -1365,6 +1367,7 @@ svc_db_identity_ensure() {
IFS=$'\t' read -r iid dbref data <<<"$line" IFS=$'\t' read -r iid dbref data <<<"$line"
local out rc=0 local out rc=0
out="$(svc_identity_run "$bin" ensure "$iid" "$dbref" "$data" 2>&1)" || rc=$? out="$(svc_identity_run "$bin" ensure "$iid" "$dbref" "$data" 2>&1)" || rc=$?
SVC_IDENTITY_LAST_OUT="$out"
if [ "$rc" -ne 0 ]; then if [ "$rc" -ne 0 ]; then
c_err "[$ENV_NAME] DB 身份 ensure 失败:$out" c_err "[$ENV_NAME] DB 身份 ensure 失败:$out"
return 1 return 1
@@ -1374,6 +1377,7 @@ svc_db_identity_ensure() {
} }
svc_db_identity_verify() { svc_db_identity_verify() {
SVC_IDENTITY_LAST_OUT=""
manifest_ok || return 0 manifest_ok || return 0
local kind; kind="$(svc_get kind)" local kind; kind="$(svc_get kind)"
[ "$kind" = agent ] || return 0 [ "$kind" = agent ] || return 0
@@ -1384,6 +1388,7 @@ svc_db_identity_verify() {
IFS=$'\t' read -r iid dbref data <<<"$line" IFS=$'\t' read -r iid dbref data <<<"$line"
local out rc=0 local out rc=0
out="$(svc_identity_run "$bin" verify "$iid" "$dbref" "$data" 2>&1)" || rc=$? out="$(svc_identity_run "$bin" verify "$iid" "$dbref" "$data" 2>&1)" || rc=$?
SVC_IDENTITY_LAST_OUT="$out"
if [ "$rc" -ne 0 ]; then if [ "$rc" -ne 0 ]; then
c_err "[$ENV_NAME] DB 身份校验失败,拒绝启动:$out" c_err "[$ENV_NAME] DB 身份校验失败,拒绝启动:$out"
c_err " 先调查库文件与身份行;确认归属后 scripts/svc.sh $ENV_NAME adopt --confirm $iid" c_err " 先调查库文件与身份行;确认归属后 scripts/svc.sh $ENV_NAME adopt --confirm $iid"
@@ -1395,6 +1400,22 @@ svc_db_identity_verify() {
return 0 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:部署候选的身份门禁——在停旧服务、覆盖制品之前 # svc_deploy_identity_gate SRCBIN:部署候选的身份门禁——在停旧服务、覆盖制品之前
# 执行(2026-10-12 rdm1/rdmcand 事故:停服→覆盖→启动即被身份门禁拒绝,只剩 # 执行(2026-10-12 rdm1/rdmcand 事故:停服→覆盖→启动即被身份门禁拒绝,只剩
# 「登记版本≠磁盘制品」的混合状态且无恢复路径)。规则: # 「登记版本≠磁盘制品」的混合状态且无恢复路径)。规则:
@@ -1425,7 +1446,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")" 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 if [ -f "$data_root/agent-system.db" ] || { [ "${DB_MODE:-}" = pg ] && [ -n "${DB_DSN:-}" ]; }; then
svc_db_identity_ensure "$srcbin" || { 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 return 1
} }
fi fi
@@ -1433,7 +1455,8 @@ svc_deploy_identity_gate() {
*) *)
# established:只验证,不补行。缺行或不匹配在停旧服务前拒绝。 # established:只验证,不补行。缺行或不匹配在停旧服务前拒绝。
svc_db_identity_verify "$srcbin" || { svc_db_identity_verify "$srcbin" || {
c_err "[$ENV_NAME] 候选无法验证已建立的 DB 身份;旧服务/旧制品/登记保持不变" svc_identity_schema_missing "$srcbin" \
|| c_err "[$ENV_NAME] 候选无法验证已建立的 DB 身份;旧服务/旧制品/登记保持不变"
return 1 return 1
} }
;; ;;
@@ -6507,7 +6530,13 @@ finally:
PY PY
svc_db_marker_set pending || exit 1 svc_db_marker_set pending || exit 1
svc_db_identity_ensure || { c_err "[$ENV_NAME] adopt ensure 失败"; 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 ;; status) env_status_detail ;;
logs) [ -f "$ENV_LOG" ] && tail -n "${CLI_LOGS_N:-50}" -f "$ENV_LOG" || c_warn "[$ENV_NAME] 暂无日志" ;; logs) [ -f "$ENV_LOG" ] && tail -n "${CLI_LOGS_N:-50}" -f "$ENV_LOG" || c_warn "[$ENV_NAME] 暂无日志" ;;
esac esac
+45
View File
@@ -413,6 +413,51 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
hang.chmod(0o755) hang.chmod(0o755)
self.assertIn("rc=1", probe(hang), "探测挂死须有界返回无能力") 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): def test_I3_start_ensures_sqlite_identity(self):
self.seed_agent("idb1", 9441) self.seed_agent("idb1", 9441)
self.start_ok("idb1") self.start_ok("idb1")