Merge authoritative main into the task branch

Why: include the current integration baseline before verification.

Evidence: fetched origin/main=2c016931044bebdc4a114c1ca1127d1b40d4ab17.

Impact: integrate upstream changes without rewriting task history.

Refs: task-sync
This commit is contained in:
2026-10-12 16:22:27 +00:00
5 changed files with 2044 additions and 33 deletions
File diff suppressed because one or more lines are too long
+6 -4
View File
@@ -2000,11 +2000,11 @@ svc_release_adopt_legacy() {
c_info "[$ENV_NAME] 领养 legacy 制品为首个 release:releases/$id"
}
# svc_release_id:源标签净化的版本目录名(rdm-ddc09eb-20261012T131500)。
# svc_release_id:源标签 + 时钟 + 调用进程身份,同源同秒部署仍使用不同目录。
svc_release_id() {
local label
label="$(printf '%s' "${SRC_LABEL:-manual}" | tr '@' '-' | tr -cd 'a-zA-Z0-9._-' | cut -c1-40)"
printf '%s-%s' "$label" "$(date -u +%Y%m%dT%H%M%SZ)"
printf '%s-%s-%s' "$label" "$(date -u +%Y%m%dT%H%M%SZ)" "$BASHPID"
}
# svc_release_stage ID SRCBIN SRCPLUGINS [HTDOCS]:候选产物入不可变版本目录。
@@ -2012,8 +2012,10 @@ svc_release_id() {
svc_release_stage() {
local id="$1" srcbin="$2" srcplug="$3" htdocs="${4:-}"
local dir="$ENV_DIR/releases/$id"
rm -rf "$dir"
mkdir -p "$dir/bin" || return 1
mkdir -p "$ENV_DIR/releases" || return 1
# 排他创建:重复 id 必须拒绝,不能覆盖仍用于运行或回退的历史制品。
mkdir "$dir" || { c_err "[$ENV_NAME] release 目录已存在或无法创建:$dir"; return 1; }
mkdir "$dir/bin" || return 1
cp -f "$srcbin" "$dir/bin/agent-system" || return 1
if [ -d "$srcplug" ]; then
cp -r "$srcplug" "$dir/plugins" || return 1
+48 -28
View File
@@ -18,6 +18,7 @@ import importlib.util
import json
import os
import shutil
import socket
import sqlite3
import subprocess
import tempfile
@@ -110,6 +111,52 @@ class RealComboTests(tsr.SvcAutoRecoverTests):
("exec", "--addr 127.0.0.1:%d --data %s --plugins %s --auth auto"
% (port, d / "data", d / "plugins")),
]))
self.initialize_store(binary, d / "data")
def initialize_store(self, binary, data, env=None):
"""Create the fixture schema through normal startup before identity CLI.
identity ensure/verify operate on an existing store and deliberately do
not create a missing database or migrate an uninitialized one.
"""
init_env = dict(self.env if env is None else env)
init_env["RDM_DISABLE_EXEC_TOOLS"] = "1"
if env is None:
init_env.pop("AGENT_SYSTEM_DSN", None)
with socket.socket() as sock:
sock.bind(("127.0.0.1", 0))
port = sock.getsockname()[1]
log_path = self.root / ("initialize-%s.log" % data.name)
with log_path.open("wb") as log:
proc = subprocess.Popen(
[str(binary), "serve", "--addr", "127.0.0.1:%d" % port,
"--data", str(data), "--auth", "on"],
cwd=self.root, env=init_env, stdout=log, stderr=log)
try:
for _ in range(200):
self.assertIsNone(proc.poll(),
"fixture startup exited; see %s" % log_path)
try:
urllib.request.urlopen(
"http://127.0.0.1:%d/api/health" % port, timeout=1).close()
break
except urllib.error.HTTPError as response:
status = response.code
response.close()
if status == 401:
break # Authenticated health returning 401 is ready too.
self.fail("fixture health returned %d; see %s" % (status, log_path))
except OSError:
time.sleep(0.1)
else:
self.fail("fixture startup timed out; see %s" % log_path)
finally:
proc.terminate()
try:
proc.wait(timeout=10)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait(timeout=10)
@unittest.skipUnless(REAL and REAL_BIN,
"真实基座二进制专项 NOT_RUN:需 SVC_REAL_BIN")
@@ -227,34 +274,7 @@ class RealComboTests(tsr.SvcAutoRecoverTests):
env["AGENT_SYSTEM_DSN"] = os.environ["AGENT_PG_TEST_DSN"]
env["RDM_DISABLE_EXEC_TOOLS"] = "1"
env.pop("SVC_STUB_TRACE", None)
# 身份命令不建库、不迁移。先让 serve 把库迁到当前 schema,再 ensure/verify。
port = 9475
log = self.root / "pg-serve.log"
serve = subprocess.Popen(
[str(rdm_bin), "serve", "--addr", "127.0.0.1:%d" % port,
"--data", str(data), "--plugins", str(plugins), "--auth", "auto"],
env=env, stdout=log.open("w"), stderr=subprocess.STDOUT)
try:
up = False
for _ in range(50):
if serve.poll() is not None:
break
try:
urllib.request.urlopen("http://127.0.0.1:%d/" % port, timeout=1).close()
up = True
break
except urllib.error.HTTPError:
up = True
break
except OSError:
time.sleep(0.1)
self.assertTrue(up, "serve 未完成迁移:\n%s" % log.read_text(errors="replace")[-1500:])
finally:
serve.terminate()
try:
serve.wait(timeout=5)
except subprocess.TimeoutExpired:
serve.kill()
self.initialize_store(rdm_bin, data, env)
iid = "svc-realcombo-pg-%d" % os.getpid()
run = lambda *a: subprocess.run(
[str(rdm_bin), "identity", *a, "--instance-id", iid,
+16
View File
@@ -9,6 +9,7 @@ tests/test_svc_identity.py(IdentityTests:manifest 治理 + 源仓轨道 + no
import importlib.util
import json
import os
import shlex
import shutil
import subprocess
import unittest
@@ -89,6 +90,21 @@ class U2ReleaseTests(tsi.IdentityTests):
# ---- T1:release 布局 + 启动失败回退 + 期望态恢复 ----
def test_U2_layout_switch_and_rollback_on_start_failure(self):
# Both deployments intentionally share the same clock tick: a failed
# candidate must never overwrite the version needed for rollback.
clock_bin = self.root / "fixed-clock"
clock_bin.mkdir()
real_date = shutil.which("date")
clock = clock_bin / "date"
clock.write_text(
'#!/bin/bash\n'
'if [[ "$*" == *"+%Y%m%dT%H%M%SZ"* ]]; then\n'
' echo 20261012T120000Z\n'
'else\n'
f' exec {shlex.quote(real_date)} "$@"\n'
'fi\n')
clock.chmod(0o755)
self.env["PATH"] = str(clock_bin) + os.pathsep + self.env["PATH"]
self.make_src_repo() # 预克隆 srccache(本用例走 --from local,仅保环境一致)
self.seed_agent("u2a", 9461)
self.start_ok("u2a") # legacy 运行中(期望态=running)