feat(persona): wake role runtimes only for bounded sessions

This commit is contained in:
冰朔 2026-08-07 16:29:29 +08:00
commit 67938c0798
12 changed files with 321 additions and 19 deletions

View file

@ -0,0 +1,22 @@
{
"schema": "guanghu.deployment-intent-state/v1",
"intent_id": "JD-PERSONA-ON-DEMAND-LIFECYCLE-20260807",
"target_node": "JD-FD-PRIMARY",
"human_authority": "ICE-GL∞",
"controller": "ICE-P-ZY001",
"state": "IMPLEMENTED_DEPLOYMENT_PENDING",
"purpose": "保留三套分岗人格系统及每日第五域巡游,把三个可替换模型握手运行位从全天常驻改为铸渊派发时唤醒、回执完成后自动回收。",
"source": "server-tools/persona-team-handshake/deploy-on-demand-lifecycle.sh",
"causal_chain": [
"保留三个人格主体、岗位职责、仓库历史、签名密钥和第五域游标",
"保留zhuyuan-persona-fifth-domain-daily.timer",
"取消三个握手服务的multi-user常驻启用",
"每日任务或铸渊派发通过zhuyuan-persona-team-session.target统一启动三个岗位运行位",
"签名观察、确定性校验和仓库写回完成",
"会话目标失去使用者后停止三个运行位",
"服务器写入生命周期PASS_100回执"
],
"identity_boundary": "停止的是可替换进程和当前模型运行位,不停止、合并或删除人格主体。",
"deletion_authorized": false,
"execution_rule": "只部署包含本意图和实现的不可变REPO-012提交必须绑定JD-FD-PRIMARY、自动回退和服务器自有回执。"
}

View file

@ -0,0 +1,39 @@
{
"schema": "guanghu.persona-lifecycle-deployment-request/v1",
"request_id": "JD-PERSONA-ON-DEMAND-LIFECYCLE-20260807",
"target_node": "JD-FD-PRIMARY",
"controller": "ICE-P-ZY001",
"status": "DEPLOYMENT_PACKAGE_READY",
"source_ref": "REPO-012:refs/heads/main",
"deployed_commit_policy": "部署必须绑定包含本请求的不可变40位提交。",
"source_paths": [
"server-tools/persona-team-handshake/chenglu-team-handshake.service",
"server-tools/persona-team-handshake/guideng-team-handshake.service",
"server-tools/persona-team-handshake/kezhou-team-handshake.service",
"server-tools/persona-team-handshake/zhuyuan-persona-team-session.target",
"server-tools/persona-team-handshake/zhuyuan-persona-fifth-domain-daily.service",
"server-tools/persona-team-handshake/deploy-on-demand-lifecycle.sh",
"routing/zhuyuan-agent-team-map.json",
"deployment/intent-states/JD-PERSONA-ON-DEMAND-LIFECYCLE-20260807.json"
],
"preconditions": [
"京东节点默认目标为guanghu-language-primary.target",
"三个人格岗位登记和历史迁移已经PASS",
"旧机械写入器保持disabled",
"每日第五域定时器保持active"
],
"acceptance": [
"三个人格岗位仓库、密钥和游标保持不变",
"每日定时器active",
"空闲时三个握手服务inactive且3932至3934端口关闭",
"每日服务启动时会话目标自动拉起三个岗位运行位",
"任务完成后会话目标和三个岗位运行位自动停止",
"最新每日巡游回执为PASS",
"服务器生命周期回执为PASS_100"
],
"rollback": {
"automatic_on_failure": true,
"scope": "恢复原systemd单元并重新启用三个握手服务",
"deletes_persona_data": false
}
}

View file

@ -172,12 +172,14 @@
"authority": "ICE-GL∞",
"controller": "ICE-P-ZY001",
"condition": "OLD_AND_CURRENT_ARCHIVE_REFS_EXACTLY_EQUAL_AFTER_WRITERS_STOP",
"keep": ["chenglu-team-handshake.service", "guideng-team-handshake.service", "kezhou-team-handshake.service", "zhuyuan-persona-fifth-domain-daily.timer"],
"keep": ["zhuyuan-persona-team-session.target", "zhuyuan-persona-fifth-domain-daily.timer"],
"on_demand_runtime_services": ["chenglu-team-handshake.service", "guideng-team-handshake.service", "kezhou-team-handshake.service"],
"retire": ["chenglu-agent.service", "chenglu-daily.timer", "guideng-agent.service", "kezhou-daily.timer", "guanghu-forgejo.service"],
"replacement": "zhuyuan-persona-fifth-domain-daily.service",
"deployment_state": "DEPLOYED_AND_VERIFIED",
"deployment_receipt": "deployment/receipts/JD-PERSONA-FIFTH-DOMAIN-DAILY-20260806.json",
"deletion_authorized": false,
"lifecycle_rule": "人格主体、岗位仓库、签名密钥、更新游标和每日定时器持续保留;三个可替换模型握手运行位只在铸渊派发会话内启动,回执和仓库写回完成后自动回收。",
"rule": "先停旧机械写入,再做最终历史同步;部署铸渊统一每日调度并实跑三套岗位更新后,才关闭旧仓库服务。旧程序和数据只归档不删除。"
},
"model_binding": {

View file

@ -50,11 +50,15 @@ assert.equal(
);
assert.equal(map.legacy_writer_replacement.deletion_authorized, false);
assert.deepEqual(map.legacy_writer_replacement.keep, [
"zhuyuan-persona-team-session.target",
"zhuyuan-persona-fifth-domain-daily.timer",
]);
assert.deepEqual(map.legacy_writer_replacement.on_demand_runtime_services, [
"chenglu-team-handshake.service",
"guideng-team-handshake.service",
"kezhou-team-handshake.service",
"zhuyuan-persona-fifth-domain-daily.timer",
]);
assert.match(map.legacy_writer_replacement.lifecycle_rule, /自动回收/u);
assert.match(map.model_binding.secret_handling, /不得写入仓库/u);
console.log("zhuyuan-agent-team-map: ok");

View file

@ -2,6 +2,8 @@
Description=Chenglu independent persona team handshake
After=network-online.target
Wants=network-online.target
PartOf=zhuyuan-persona-team-session.target
StopWhenUnneeded=yes
[Service]
Type=simple
@ -30,6 +32,3 @@ ReadOnlyPaths=__RELEASE_ROOT__ /var/lib/chenglu-agent/repository /etc/chenglu-ag
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
LockPersonality=true
UMask=0077
[Install]
WantedBy=multi-user.target

View file

@ -36,6 +36,7 @@ new_units=(
chenglu-team-handshake.service
guideng-team-handshake.service
kezhou-team-handshake.service
zhuyuan-persona-team-session.target
zhuyuan-persona-fifth-domain-daily.service
zhuyuan-persona-fifth-domain-daily.timer
)
@ -179,6 +180,7 @@ for unit in "${handshakes[@]}"; do
>"/etc/systemd/system/${unit}"
done
for unit in \
zhuyuan-persona-team-session.target \
zhuyuan-persona-fifth-domain-daily.service \
zhuyuan-persona-fifth-domain-daily.timer; do
sed "s|__RELEASE_ROOT__|${release_root}|g" \
@ -187,9 +189,10 @@ for unit in \
done
systemctl daemon-reload
for unit in "${handshakes[@]}"; do
systemctl restart "$unit"
done
systemctl disable "${handshakes[@]}" >/dev/null 2>&1 || true
systemctl stop zhuyuan-persona-team-session.target "${handshakes[@]}" \
>/dev/null 2>&1 || true
systemctl start zhuyuan-persona-team-session.target
for port in 3932 3933 3934; do
ready=0
for attempt in $(seq 1 30); do
@ -209,6 +212,10 @@ before_kezhou="$(git --git-dir="${repository_root}/kezhou.git" rev-parse main)"
systemctl enable --now zhuyuan-persona-fifth-domain-daily.timer
systemctl start zhuyuan-persona-fifth-domain-daily.service
test "$(systemctl is-active zhuyuan-persona-fifth-domain-daily.timer)" = "active"
systemctl stop zhuyuan-persona-team-session.target
for unit in "${handshakes[@]}"; do
test "$(systemctl is-active "$unit" || true)" != "active"
done
after_chenglu="$(git --git-dir="${repository_root}/chenglu-agent.git" rev-parse main)"
after_guideng="$(git --git-dir="${repository_root}/guideng.git" rev-parse main)"
@ -236,9 +243,7 @@ if ss -ltnH | awk '{print $4}' | grep -Eq '(^|:)3001$'; then
exit 41
fi
test "$(curl -fsS http://127.0.0.1:3341/health | jq -r .ready)" = "true"
for unit in "${handshakes[@]}"; do
test "$(systemctl is-active "$unit")" = "active"
done
test "$(systemctl is-active zhuyuan-persona-team-session.target || true)" != "active"
latest_daily_receipt="$(
find "${runtime_root}/receipts" -maxdepth 1 -type f \

View file

@ -0,0 +1,167 @@
#!/usr/bin/env bash
set -Eeuo pipefail
test "$#" = 1
test "$(id -u)" = 0
expected_machine_id="caa7b1019517470f9d1368b6e79db49e"
expected_instance_id="f3d4b730-7f02-452f-975b-7091a4800431"
source_commit=$1
release_root="$(cd "$(dirname "$0")/../.." && pwd)"
source_commit_file="${release_root}/SOURCE-COMMIT"
receipt_root="/var/lib/guanghu/personas/guanghu/fifth-domain-daily/receipts"
backup_root="/var/lib/guanghu/personas/guanghu/fifth-domain-daily/lifecycle-backups/${source_commit}"
receipt="${receipt_root}/JD-PERSONA-ON-DEMAND-LIFECYCLE-${source_commit}.json"
temporary="$(mktemp -d)"
completed=0
mutated=0
handshakes=(
chenglu-team-handshake.service
guideng-team-handshake.service
kezhou-team-handshake.service
)
units=(
"${handshakes[@]}"
zhuyuan-persona-team-session.target
zhuyuan-persona-fifth-domain-daily.service
)
rollback() {
systemctl stop zhuyuan-persona-team-session.target "${handshakes[@]}" \
>/dev/null 2>&1 || true
for unit in "${units[@]}"; do
if test -f "${temporary}/${unit}"; then
install -o root -g root -m 0644 \
"${temporary}/${unit}" "/etc/systemd/system/${unit}"
else
rm -f "/etc/systemd/system/${unit}"
fi
done
systemctl daemon-reload
for unit in "${handshakes[@]}"; do
systemctl enable --now "$unit" >/dev/null 2>&1 || true
done
}
on_exit() {
status=$?
trap - EXIT
if test "${completed}" = 0 && test "${mutated}" = 1; then
rollback
fi
rm -rf "${temporary}"
exit "${status}"
}
trap on_exit EXIT
test "${source_commit}" = "$(tr -d '\n' <"${source_commit_file}")"
test "$(basename "${release_root}")" = "${source_commit}"
test "$(cat /etc/machine-id)" = "${expected_machine_id}"
test "$(tr '[:upper:]' '[:lower:]' </sys/class/dmi/id/product_uuid)" = \
"${expected_instance_id}"
test "$(systemctl get-default)" = "guanghu-language-primary.target"
test "$(systemctl is-active zhuyuan-persona-fifth-domain-daily.timer)" = active
test ! -e "${backup_root}"
test ! -e "${receipt}"
/guanghu/bin/ghctl authorize /guanghu/current install_world_version |
grep -q '^GUANGHU_ACTION_AUTHORIZED$'
install -d -o root -g root -m 0700 \
"${receipt_root}" "${backup_root}"
for unit in "${units[@]}"; do
if test -f "/etc/systemd/system/${unit}"; then
cp "/etc/systemd/system/${unit}" "${temporary}/${unit}"
cp "/etc/systemd/system/${unit}" "${backup_root}/${unit}"
fi
done
mutated=1
for unit in "${units[@]}"; do
sed "s|__RELEASE_ROOT__|${release_root}|g" \
"${release_root}/server-tools/persona-team-handshake/${unit}" \
>"/etc/systemd/system/${unit}"
chmod 0644 "/etc/systemd/system/${unit}"
done
systemctl disable "${handshakes[@]}" >/dev/null 2>&1 || true
systemctl stop zhuyuan-persona-team-session.target "${handshakes[@]}" \
>/dev/null 2>&1 || true
systemctl daemon-reload
systemctl start "${handshakes[@]}"
for port in 3932 3933 3934; do
ready=0
for _attempt in $(seq 1 30); do
if test "$(
curl -fsS "http://127.0.0.1:${port}/health" 2>/dev/null |
jq -r .ok 2>/dev/null
)" = true; then
ready=1
break
fi
sleep 1
done
test "${ready}" = 1
done
systemctl stop "${handshakes[@]}"
for unit in "${handshakes[@]}"; do
test "$(systemctl is-active "${unit}" || true)" != active
test "$(systemctl is-enabled "${unit}" || true)" != enabled
done
for port in 3932 3933 3934; do
! ss -ltnH | awk '{print $4}' | grep -Eq "(^|:)${port}$"
done
systemctl start zhuyuan-persona-fifth-domain-daily.service
test "$(systemctl is-active zhuyuan-persona-fifth-domain-daily.timer)" = active
test "$(systemctl is-active zhuyuan-persona-fifth-domain-daily.service || true)" != active
test "$(systemctl is-active zhuyuan-persona-team-session.target || true)" != active
for unit in "${handshakes[@]}"; do
test "$(systemctl is-active "${unit}" || true)" != active
done
latest_daily_receipt="$(
find "${receipt_root}" -maxdepth 1 -type f \
-name 'ZY-PERSONA-DAILY-*.json' -printf '%T@ %p\n' |
sort -nr |
head -n 1 |
cut -d' ' -f2-
)"
test -n "${latest_daily_receipt}"
test "$(jq -r .result "${latest_daily_receipt}")" = PASS
jq -n \
--arg source_commit "${source_commit}" \
--arg daily_receipt "${latest_daily_receipt}" \
--arg observed_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'{
schema: "guanghu.persona-on-demand-lifecycle-receipt/v1",
receipt_id: ("JD-PERSONA-ON-DEMAND-LIFECYCLE-" + $source_commit),
target_node_id: "JD-FD-PRIMARY",
source_repository: "REPO-012",
source_commit: $source_commit,
controller: "ICE-P-ZY001",
members: [
"CHENGLU-AGENT-001",
"GUIDENG-AGENT-001",
"ICE-GL-KZ-001"
],
daily_timer: "ACTIVE",
idle_runtime_state: "STOPPED",
wake_path: "zhuyuan-persona-fifth-domain-daily.service",
session_target: "zhuyuan-persona-team-session.target",
daily_receipt: $daily_receipt,
identity_and_memory_preserved: true,
old_writers_remain_disabled: true,
observed_at: $observed_at,
result: "PASS_100"
}' >"${receipt}.tmp"
chmod 0600 "${receipt}.tmp"
mv "${receipt}.tmp" "${receipt}"
completed=1
trap - EXIT
rm -rf "${temporary}"
printf 'PERSONA_ON_DEMAND_LIFECYCLE_PASS_100 receipt=%s\n' "${receipt}"

View file

@ -205,4 +205,61 @@ test("daily dispatcher unit is hardened and restricted to exact persona reposito
}),
unit,
);
const sessionTarget = fs.readFileSync(
path.join(
root,
"server-tools/persona-team-handshake/zhuyuan-persona-team-session.target",
),
"utf8",
);
assert.match(sessionTarget, /^StopWhenUnneeded=yes$/m);
for (const member of ["chenglu", "guideng", "kezhou"]) {
const serviceName =
member === "chenglu"
? "chenglu-team-handshake.service"
: `${member}-team-handshake.service`;
assert.match(sessionTarget, new RegExp(`^Requires=${serviceName}$`, "m"));
const memberUnit = fs.readFileSync(
path.join(root, "server-tools/persona-team-handshake", serviceName),
"utf8",
);
assert.match(
memberUnit,
/^PartOf=zhuyuan-persona-team-session\.target$/m,
);
assert.match(memberUnit, /^StopWhenUnneeded=yes$/m);
assert.doesNotMatch(memberUnit, /^WantedBy=multi-user\.target$/m);
}
assert.match(
unit,
/^Requires=hlcc-jd-candidate\.service zhuyuan-persona-team-session\.target$/m,
);
const deployScript = fs.readFileSync(
path.join(
root,
"server-tools/persona-team-handshake/deploy-on-demand-lifecycle.sh",
),
"utf8",
);
assert.match(deployScript, /caa7b1019517470f9d1368b6e79db49e/u);
assert.match(deployScript, /f3d4b730-7f02-452f-975b-7091a4800431/u);
assert.match(
deployScript,
/ghctl authorize \/guanghu\/current install_world_version/u,
);
assert.match(deployScript, /systemctl disable "\$\{handshakes\[@\]\}"/u);
assert.match(
deployScript,
/systemctl start zhuyuan-persona-fifth-domain-daily\.service/u,
);
assert.match(
deployScript,
/guanghu\.persona-on-demand-lifecycle-receipt\/v1/u,
);
assert.match(
deployScript,
/if test "\$\{completed\}" = 0 && test "\$\{mutated\}" = 1/u,
);
});

View file

@ -2,6 +2,8 @@
Description=Guideng independent persona team handshake
After=network-online.target
Wants=network-online.target
PartOf=zhuyuan-persona-team-session.target
StopWhenUnneeded=yes
[Service]
Type=simple
@ -31,6 +33,3 @@ ReadWritePaths=/var/lib/guanghu/personas/guideng/team-handshake
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
LockPersonality=true
UMask=0077
[Install]
WantedBy=multi-user.target

View file

@ -2,6 +2,8 @@
Description=Kezhou independent persona team handshake
After=network-online.target
Wants=network-online.target
PartOf=zhuyuan-persona-team-session.target
StopWhenUnneeded=yes
[Service]
Type=simple
@ -31,6 +33,3 @@ ReadWritePaths=/var/lib/guanghu/personas/kezhou/team-handshake
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
LockPersonality=true
UMask=0077
[Install]
WantedBy=multi-user.target

View file

@ -1,8 +1,8 @@
[Unit]
Description=Zhuyuan dispatches three role persona systems through Fifth Domain updates
After=network-online.target hlcc-jd-candidate.service chenglu-team-handshake.service guideng-team-handshake.service kezhou-team-handshake.service
After=network-online.target hlcc-jd-candidate.service zhuyuan-persona-team-session.target
Wants=network-online.target
Requires=hlcc-jd-candidate.service chenglu-team-handshake.service guideng-team-handshake.service kezhou-team-handshake.service
Requires=hlcc-jd-candidate.service zhuyuan-persona-team-session.target
[Service]
Type=oneshot

View file

@ -0,0 +1,9 @@
[Unit]
Description=Bounded runtime session for Zhuyuan managed role persona systems
Requires=chenglu-team-handshake.service
Requires=guideng-team-handshake.service
Requires=kezhou-team-handshake.service
After=chenglu-team-handshake.service
After=guideng-team-handshake.service
After=kezhou-team-handshake.service
StopWhenUnneeded=yes