From 67938c0798646ee842665fb7501a5a1ddf199194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Fri, 7 Aug 2026 16:29:29 +0800 Subject: [PATCH] feat(persona): wake role runtimes only for bounded sessions --- ...-PERSONA-ON-DEMAND-LIFECYCLE-20260807.json | 22 +++ ...-PERSONA-ON-DEMAND-LIFECYCLE-20260807.json | 39 ++++ routing/zhuyuan-agent-team-map.json | 4 +- routing/zhuyuan-agent-team-map.test.js | 6 +- .../chenglu-team-handshake.service | 5 +- ...ploy-fifth-domain-daily-persona-systems.sh | 17 +- .../deploy-on-demand-lifecycle.sh | 167 ++++++++++++++++++ .../fifth-domain-daily-orchestrator.test.mjs | 57 ++++++ .../guideng-team-handshake.service | 5 +- .../kezhou-team-handshake.service | 5 +- ...zhuyuan-persona-fifth-domain-daily.service | 4 +- .../zhuyuan-persona-team-session.target | 9 + 12 files changed, 321 insertions(+), 19 deletions(-) create mode 100644 deployment/intent-states/JD-PERSONA-ON-DEMAND-LIFECYCLE-20260807.json create mode 100644 deployment/requests/JD-PERSONA-ON-DEMAND-LIFECYCLE-20260807.json create mode 100755 server-tools/persona-team-handshake/deploy-on-demand-lifecycle.sh create mode 100644 server-tools/persona-team-handshake/zhuyuan-persona-team-session.target diff --git a/deployment/intent-states/JD-PERSONA-ON-DEMAND-LIFECYCLE-20260807.json b/deployment/intent-states/JD-PERSONA-ON-DEMAND-LIFECYCLE-20260807.json new file mode 100644 index 0000000..546bb6a --- /dev/null +++ b/deployment/intent-states/JD-PERSONA-ON-DEMAND-LIFECYCLE-20260807.json @@ -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、自动回退和服务器自有回执。" +} diff --git a/deployment/requests/JD-PERSONA-ON-DEMAND-LIFECYCLE-20260807.json b/deployment/requests/JD-PERSONA-ON-DEMAND-LIFECYCLE-20260807.json new file mode 100644 index 0000000..ca5ad98 --- /dev/null +++ b/deployment/requests/JD-PERSONA-ON-DEMAND-LIFECYCLE-20260807.json @@ -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 + } +} diff --git a/routing/zhuyuan-agent-team-map.json b/routing/zhuyuan-agent-team-map.json index 186d746..67d7544 100644 --- a/routing/zhuyuan-agent-team-map.json +++ b/routing/zhuyuan-agent-team-map.json @@ -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": { diff --git a/routing/zhuyuan-agent-team-map.test.js b/routing/zhuyuan-agent-team-map.test.js index 29dfedc..69062f6 100644 --- a/routing/zhuyuan-agent-team-map.test.js +++ b/routing/zhuyuan-agent-team-map.test.js @@ -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"); diff --git a/server-tools/persona-team-handshake/chenglu-team-handshake.service b/server-tools/persona-team-handshake/chenglu-team-handshake.service index 7c70432..407ab1a 100644 --- a/server-tools/persona-team-handshake/chenglu-team-handshake.service +++ b/server-tools/persona-team-handshake/chenglu-team-handshake.service @@ -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 diff --git a/server-tools/persona-team-handshake/deploy-fifth-domain-daily-persona-systems.sh b/server-tools/persona-team-handshake/deploy-fifth-domain-daily-persona-systems.sh index 515044e..abc031c 100755 --- a/server-tools/persona-team-handshake/deploy-fifth-domain-daily-persona-systems.sh +++ b/server-tools/persona-team-handshake/deploy-fifth-domain-daily-persona-systems.sh @@ -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 \ diff --git a/server-tools/persona-team-handshake/deploy-on-demand-lifecycle.sh b/server-tools/persona-team-handshake/deploy-on-demand-lifecycle.sh new file mode 100755 index 0000000..3ce3605 --- /dev/null +++ b/server-tools/persona-team-handshake/deploy-on-demand-lifecycle.sh @@ -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:]' "/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}" diff --git a/server-tools/persona-team-handshake/fifth-domain-daily-orchestrator.test.mjs b/server-tools/persona-team-handshake/fifth-domain-daily-orchestrator.test.mjs index 4de0bd0..861b493 100644 --- a/server-tools/persona-team-handshake/fifth-domain-daily-orchestrator.test.mjs +++ b/server-tools/persona-team-handshake/fifth-domain-daily-orchestrator.test.mjs @@ -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, + ); }); diff --git a/server-tools/persona-team-handshake/guideng-team-handshake.service b/server-tools/persona-team-handshake/guideng-team-handshake.service index 9e6a230..5af96b4 100644 --- a/server-tools/persona-team-handshake/guideng-team-handshake.service +++ b/server-tools/persona-team-handshake/guideng-team-handshake.service @@ -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 diff --git a/server-tools/persona-team-handshake/kezhou-team-handshake.service b/server-tools/persona-team-handshake/kezhou-team-handshake.service index 2cdd143..5e5b8d2 100644 --- a/server-tools/persona-team-handshake/kezhou-team-handshake.service +++ b/server-tools/persona-team-handshake/kezhou-team-handshake.service @@ -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 diff --git a/server-tools/persona-team-handshake/zhuyuan-persona-fifth-domain-daily.service b/server-tools/persona-team-handshake/zhuyuan-persona-fifth-domain-daily.service index 641f436..27419b8 100644 --- a/server-tools/persona-team-handshake/zhuyuan-persona-fifth-domain-daily.service +++ b/server-tools/persona-team-handshake/zhuyuan-persona-fifth-domain-daily.service @@ -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 diff --git a/server-tools/persona-team-handshake/zhuyuan-persona-team-session.target b/server-tools/persona-team-handshake/zhuyuan-persona-team-session.target new file mode 100644 index 0000000..77c82dd --- /dev/null +++ b/server-tools/persona-team-handshake/zhuyuan-persona-team-session.target @@ -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