Merge pull request 'Require the real lifecycle gate and fault rollback' (#495) from feature/svc-d3-lifecycle-gate into main
Why: keep fault rollback and the real lifecycle gate on main.
Evidence: GATE run 2784 jobs 4032/4033/4034 success on c197b7565b.
Impact: test-pg runs make test-lifecycle. Login stays NOT_RUN.
Refs: #495
This commit was merged in pull request #495.
This commit is contained in:
@@ -409,15 +409,25 @@ jobs:
|
|||||||
GIT_CONFIG_VALUE_2 GIT_CONFIG_KEY_3 GIT_CONFIG_VALUE_3
|
GIT_CONFIG_VALUE_2 GIT_CONFIG_KEY_3 GIT_CONFIG_VALUE_3
|
||||||
unset SUBMODULE_TOKEN
|
unset SUBMODULE_TOKEN
|
||||||
make -C rdm build
|
make -C rdm build
|
||||||
- name: Real-combination identity gates over real PostgreSQL (U3, required)
|
- name: Install Node 22
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
curl -fsSL -o "$RUNNER_TEMP/node.tar.xz" https://npmmirror.com/mirrors/node/v22.23.3/node-v22.23.3-linux-x64.tar.xz
|
||||||
|
tar -C /usr/local --strip-components=1 -xJf "$RUNNER_TEMP/node.tar.xz"
|
||||||
|
- name: Build the real agent-system binary
|
||||||
|
run: make build
|
||||||
|
- name: Real-combination lifecycle gate (make test-lifecycle, required)
|
||||||
env:
|
env:
|
||||||
AGENT_PG_TEST_DSN: postgres://ci_agent:ci_job_local_only@127.0.0.1:5432/agent_test?sslmode=disable
|
AGENT_PG_TEST_DSN: postgres://ci_agent:ci_job_local_only@127.0.0.1:5432/agent_test?sslmode=disable
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
# 路径变量在脚本内解析(YAML env: 不做 shell 展开)。
|
# 路径变量在脚本内解析(YAML env: 不做 shell 展开)。
|
||||||
|
# 不设 SVC_RDM_ORIGIN:全轨 git 部署由 svc-realcombo 作业覆盖。
|
||||||
export SVC_TEST_REAL=1 SVC_TEST_PG=1
|
export SVC_TEST_REAL=1 SVC_TEST_PG=1
|
||||||
|
export SVC_REAL_BIN="$PWD/bin/agent-system"
|
||||||
export SVC_REAL_RDM_BIN="$PWD/rdm/bin/agent-system-rdm"
|
export SVC_REAL_RDM_BIN="$PWD/rdm/bin/agent-system-rdm"
|
||||||
python3 -m unittest tests.test_svc_realcombo -v
|
export SVC_RDM_SRC_DIR="$PWD/rdm"
|
||||||
|
make test-lifecycle
|
||||||
|
|
||||||
# U3(统一部署方案):真实 Dev/RDM 制品的 svc.sh 生命周期门禁——桩套件
|
# U3(统一部署方案):真实 Dev/RDM 制品的 svc.sh 生命周期门禁——桩套件
|
||||||
# (test_svc*.py)只证明脚本逻辑;本作业证明「声明≠能力」的最后一道:真实
|
# (test_svc*.py)只证明脚本逻辑;本作业证明「声明≠能力」的最后一道:真实
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -113,7 +113,7 @@ test-backend-pg:
|
|||||||
cd $(BACKEND) && go test -count=1 ./...
|
cd $(BACKEND) && go test -count=1 ./...
|
||||||
|
|
||||||
# 真实 Dev/RDM + PostgreSQL 生命周期门禁。缺环境直接失败,不静默跳过。
|
# 真实 Dev/RDM + PostgreSQL 生命周期门禁。缺环境直接失败,不静默跳过。
|
||||||
# 登录入口未就绪时套件保持失败(NOT_RUN),不把 D3 报成完成。
|
# 登录单独标 NOT_RUN(依赖账户方案 U7/U5),不把其余真实组合场景记成失败。
|
||||||
test-lifecycle:
|
test-lifecycle:
|
||||||
@test -n "$${AGENT_PG_TEST_DSN}" || { echo "test-lifecycle: AGENT_PG_TEST_DSN 未设置(真实 PostgreSQL 必需,不静默跳过)"; exit 1; }
|
@test -n "$${AGENT_PG_TEST_DSN}" || { echo "test-lifecycle: AGENT_PG_TEST_DSN 未设置(真实 PostgreSQL 必需,不静默跳过)"; exit 1; }
|
||||||
@test -n "$${SVC_RDM_SRC_DIR}" || { echo "test-lifecycle: SVC_RDM_SRC_DIR 未设置(RDM 检出必需,不静默跳过)"; exit 1; }
|
@test -n "$${SVC_RDM_SRC_DIR}" || { echo "test-lifecycle: SVC_RDM_SRC_DIR 未设置(RDM 检出必需,不静默跳过)"; exit 1; }
|
||||||
|
|||||||
+1
-1
@@ -41,7 +41,7 @@ U2 起实施与部署分别评审。
|
|||||||
- [服务重启与全量数据恢复方案 r3.1](modules/documents/docs/proposals/2026-10-12-service-restart-data-recovery.md):
|
- [服务重启与全量数据恢复方案 r3.1](modules/documents/docs/proposals/2026-10-12-service-restart-data-recovery.md):
|
||||||
svc 启停、异常进程/主机退出后的依赖、配置/数据身份、任务/HITL/副作用、文件与组合备份恢复;
|
svc 启停、异常进程/主机退出后的依赖、配置/数据身份、任务/HITL/副作用、文件与组合备份恢复;
|
||||||
r2 已批准。r3.1 增补 D1 实例身份收尾、D2 可回滚切换、D3 真实组合门禁,并记录 2026-10-12 rdmcand 数据归属:会话不在 dev 库的 rdmcand 窗口内,领域事件为空,不跨实例搬迁。
|
r2 已批准。r3.1 增补 D1 实例身份收尾、D2 可回滚切换、D3 真实组合门禁,并记录 2026-10-12 rdmcand 数据归属:会话不在 dev 库的 rdmcand 窗口内,领域事件为空,不跨实例搬迁。
|
||||||
D1/D2/D3 按实施计划推进到可合入;r3 其余未批条款仍待人工评审。
|
D1 与 D2 已合入 Backend #184、RDM #291、Dev #485。D3 的 `make test-lifecycle` 跑真实组合用例;登录仍是 NOT_RUN,依赖账户方案 U7/U5,不在本轮公共化账户 CLI。r3 其余未批条款仍待人工评审。
|
||||||
|
|
||||||
- [svc 统一部署与一次性登录 Key 方案 r1](modules/documents/docs/proposals/2026-10-12-svc-unified-deploy-and-login-key.md):
|
- [svc 统一部署与一次性登录 Key 方案 r1](modules/documents/docs/proposals/2026-10-12-svc-unified-deploy-and-login-key.md):
|
||||||
二进制/源码统一部署、环境准备、无初始密码开户与账户恢复;一次性 Key 仅兑换设密受限会话;
|
二进制/源码统一部署、环境准备、无初始密码开户与账户恢复;一次性 Key 仅兑换设密受限会话;
|
||||||
|
|||||||
@@ -2074,6 +2074,7 @@ with open(tmp, "w") as f:
|
|||||||
f.write(json.dumps(j, ensure_ascii=False) + "\n")
|
f.write(json.dumps(j, ensure_ascii=False) + "\n")
|
||||||
os.replace(tmp, out)
|
os.replace(tmp, out)
|
||||||
PYJ
|
PYJ
|
||||||
|
svc_fault_check "$phase"
|
||||||
}
|
}
|
||||||
|
|
||||||
svc_journal_get() { # KEY → 值(journal 缺失/损坏 → 空)
|
svc_journal_get() { # KEY → 值(journal 缺失/损坏 → 空)
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
"""真实 Dev/RDM 组合门禁。由 make test-lifecycle 调用。
|
"""真实 Dev/RDM 组合门禁。由 make test-lifecycle 调用。
|
||||||
|
|
||||||
缺 PostgreSQL、RDM 源码或制品时直接失败,不静默跳过。
|
缺 PostgreSQL、RDM 源码或制品时直接失败,不静默跳过。
|
||||||
RDM 登录依赖账户 CLI 公共化(账户方案 U7/U5),当前标 NOT_RUN,本套件因此不能报完成。
|
登录依赖账户 CLI 公共化(账户方案 U7/U5),单独标 NOT_RUN,
|
||||||
|
不因此把已经跑过的真实组合场景记成失败。
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -15,32 +17,42 @@ def main():
|
|||||||
missing = []
|
missing = []
|
||||||
if not os.environ.get("AGENT_PG_TEST_DSN"):
|
if not os.environ.get("AGENT_PG_TEST_DSN"):
|
||||||
missing.append("AGENT_PG_TEST_DSN")
|
missing.append("AGENT_PG_TEST_DSN")
|
||||||
if not os.environ.get("SVC_RDM_SRC_DIR") or not Path(os.environ.get("SVC_RDM_SRC_DIR", "")).is_dir():
|
src = os.environ.get("SVC_RDM_SRC_DIR", "")
|
||||||
|
if not src or not Path(src).is_dir():
|
||||||
missing.append("SVC_RDM_SRC_DIR")
|
missing.append("SVC_RDM_SRC_DIR")
|
||||||
if not (ROOT / "bin" / "agent-system").is_file() and not os.environ.get("SVC_DEV_BIN"):
|
dev = os.environ.get("SVC_REAL_BIN") or str(ROOT / "bin" / "agent-system")
|
||||||
missing.append("Dev 制品(make build 或 SVC_DEV_BIN)")
|
rdm = os.environ.get("SVC_REAL_RDM_BIN", "")
|
||||||
|
if not Path(dev).is_file():
|
||||||
|
missing.append("Dev 制品(SVC_REAL_BIN 或 bin/agent-system)")
|
||||||
|
if not rdm or not Path(rdm).is_file():
|
||||||
|
missing.append("RDM 制品(SVC_REAL_RDM_BIN)")
|
||||||
|
if missing:
|
||||||
|
print("test-lifecycle 失败。缺:%s" % ", ".join(missing))
|
||||||
|
return 1
|
||||||
|
|
||||||
print("组合清单")
|
print("组合清单")
|
||||||
print("pin", PIN.read_text().strip())
|
print("pin", PIN.read_text().strip())
|
||||||
print("login NOT_RUN:RDM 二进制没有 user/apikey,工作台登录依赖账户 CLI 公共化")
|
print("rdm_src", src)
|
||||||
scenarios = [
|
print("dev_bin", dev)
|
||||||
"双向混合批次更新",
|
print("rdm_bin", rdm)
|
||||||
"无 manifest 各入口一致",
|
print("将运行 tests.test_svc_realcombo:SQLite 身份、RDM 二进制生命周期、PostgreSQL 身份环境交付")
|
||||||
"legacy/缺行/不匹配停服前拒绝",
|
if os.environ.get("SVC_RDM_ORIGIN"):
|
||||||
"两次裸重启与换端口换版本换目录",
|
print("将运行全轨部署:SVC_RDM_ORIGIN 已设置")
|
||||||
"构建失败与启动失败回滚",
|
else:
|
||||||
"中断对账",
|
print("全轨部署 NOT_RUN:未设置 SVC_RDM_ORIGIN(由 svc-realcombo 作业覆盖)")
|
||||||
"PG 绑定与跨实例错绑拒绝",
|
print("登录 NOT_RUN:RDM 二进制没有 user/apikey,工作台登录依赖账户方案 U7/U5")
|
||||||
"凭据不进 cmdline/日志",
|
env = os.environ.copy()
|
||||||
"RDM 页面、rdm.ping、升级前领域数据仍在",
|
env["SVC_TEST_REAL"] = "1"
|
||||||
"登录",
|
env["SVC_TEST_PG"] = "1"
|
||||||
]
|
env["SVC_REAL_BIN"] = dev
|
||||||
for name in scenarios:
|
env["SVC_REAL_RDM_BIN"] = rdm
|
||||||
state = "NOT_RUN" if missing or name == "登录" else "PENDING"
|
proc = subprocess.run(
|
||||||
print("%s %s" % (state, name))
|
[sys.executable, "-m", "unittest", "tests.test_svc_realcombo", "-q"],
|
||||||
if missing or True:
|
cwd=str(ROOT), env=env)
|
||||||
# 登录依赖未就绪时保持未完成。缺环境同样失败。
|
if proc.returncode != 0:
|
||||||
print("test-lifecycle 未完成。缺:%s" % (", ".join(missing) if missing else "登录入口"))
|
print("test-lifecycle 失败:真实组合用例未通过")
|
||||||
return 1
|
return proc.returncode
|
||||||
|
print("test-lifecycle 可跑场景通过;登录与未设 origin 的全轨部署保持 NOT_RUN")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -229,6 +229,53 @@ class U2ReleaseTests(tsi.IdentityTests):
|
|||||||
|
|
||||||
# ---- T5:legacy 布局未迁移 → 裸 start/restart 仍走原路径(兼容) ----
|
# ---- T5:legacy 布局未迁移 → 裸 start/restart 仍走原路径(兼容) ----
|
||||||
|
|
||||||
|
def test_U2_fault_after_switch_rolls_back_on_next_start(self):
|
||||||
|
self.seed_agent("u2f", 9466)
|
||||||
|
self.set_makefile(self.stub)
|
||||||
|
self.svc("u2f", "deploy", "--from", "local")
|
||||||
|
v1 = self.cur_release("u2f")
|
||||||
|
self.svc("u2f", "stop")
|
||||||
|
env = dict(self.env)
|
||||||
|
env["SVC_TEST_FAULTS"] = "1"
|
||||||
|
env["SVC_FAULT_AT"] = "switched"
|
||||||
|
result = self.svc("u2f", "deploy", "--from", "local", check=False, env=env)
|
||||||
|
self.assertEqual(result.returncode, 86, result.stdout)
|
||||||
|
self.assertIn("故障注入于 switched", result.stdout)
|
||||||
|
result = self.svc("u2f", "start")
|
||||||
|
self.assertIn("已回退上一版本", result.stdout)
|
||||||
|
self.assertEqual(self.cur_release("u2f"), v1)
|
||||||
|
self.assertTrue(self.running("u2f"))
|
||||||
|
self.svc("u2f", "stop")
|
||||||
|
|
||||||
|
def test_U2_health_timeout_rolls_back(self):
|
||||||
|
self.seed_agent("u2g", 9467)
|
||||||
|
self.set_makefile(self.stub)
|
||||||
|
self.svc("u2g", "deploy", "--from", "local")
|
||||||
|
v1 = self.cur_release("u2g")
|
||||||
|
good = self.active_bin("u2g").read_bytes()
|
||||||
|
self.set_makefile(self.hang_stub())
|
||||||
|
result = self.svc("u2g", "deploy", "--from", "local", check=False)
|
||||||
|
self.assertNotEqual(result.returncode, 0, result.stdout)
|
||||||
|
self.assertIn("健康检查未通过", result.stdout)
|
||||||
|
self.assertIn("已回退上一版本", result.stdout)
|
||||||
|
self.assertEqual(self.cur_release("u2g"), v1)
|
||||||
|
self.assertEqual(self.active_bin("u2g").read_bytes(), good)
|
||||||
|
self.assertTrue(self.running("u2g"))
|
||||||
|
self.svc("u2g", "stop")
|
||||||
|
|
||||||
|
def hang_stub(self):
|
||||||
|
"""identity 可用、serve 挂起且不监听,用来触发健康检查超时。"""
|
||||||
|
p = self.root / "stub-agent-system-hang"
|
||||||
|
if not p.exists():
|
||||||
|
stub = tsr.STUB
|
||||||
|
head, sep, tail = stub.partition(" serve)")
|
||||||
|
assert sep, "STUB 缺 serve 分支锚点"
|
||||||
|
_, sep2, tail2 = tail.partition(' wait "$child"\n ;;')
|
||||||
|
assert sep2, "STUB 缺 serve 分支结尾锚点"
|
||||||
|
p.write_text(head + " serve)\n shift\n sleep 60\n exit 0 ;;" + tail2)
|
||||||
|
p.chmod(0o755)
|
||||||
|
return p
|
||||||
|
|
||||||
def test_U2_legacy_layout_bare_lifecycle_still_works(self):
|
def test_U2_legacy_layout_bare_lifecycle_still_works(self):
|
||||||
self.seed_agent("u2e", 9465)
|
self.seed_agent("u2e", 9465)
|
||||||
self.start_ok("u2e")
|
self.start_ok("u2e")
|
||||||
|
|||||||
Reference in New Issue
Block a user