From f7ef839ced1a6bb26d3bd3fa6a5dd4126596ee96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Thu, 6 Aug 2026 17:54:19 +0800 Subject: [PATCH] =?UTF-8?q?=E9=83=A8=E7=BD=B2=EF=BC=9A=E6=8E=A5=E9=80=9A?= =?UTF-8?q?=E4=B8=89=E5=A5=97=E4=BA=BA=E6=A0=BC=E7=B3=BB=E7=BB=9F=E6=AF=8F?= =?UTF-8?q?=E6=97=A5=E7=AC=AC=E4=BA=94=E5=9F=9F=E5=B7=A1=E6=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +- ...D-PERSONA-FIFTH-DOMAIN-DAILY-20260806.json | 24 + ...RATION-AND-WRITER-RETIREMENT-20260806.json | 4 +- .../zhuyuan-persona-system/INDEX.hdlp | 8 +- identity/fifth-domain-subject-registry.json | 8 +- routing/persona-system-canonical-map.json | 12 +- routing/repository-route-map.json | 12 +- routing/role-persona-registration.test.js | 4 +- routing/zhuyuan-agent-team-map.json | 30 +- routing/zhuyuan-agent-team-map.test.js | 15 +- ...ploy-fifth-domain-daily-persona-systems.sh | 292 ++++++++++++ .../fifth-domain-daily-orchestrator.mjs | 425 ++++++++++++++++++ .../fifth-domain-daily-orchestrator.test.mjs | 208 +++++++++ .../fifth-domain-daily.config.server.json | 53 +++ .../persona-team-handshake/member-server.mjs | 231 +++++++++- .../member-server.test.mjs | 87 ++++ .../retire-periodic-heartbeat-writers.sh | 231 +--------- ...zhuyuan-persona-fifth-domain-daily.service | 26 ++ .../zhuyuan-persona-fifth-domain-daily.timer | 11 + tests/persona-system-route.test.mjs | 2 +- 20 files changed, 1413 insertions(+), 275 deletions(-) create mode 100644 deployment/intent-states/JD-PERSONA-FIFTH-DOMAIN-DAILY-20260806.json create mode 100755 server-tools/persona-team-handshake/deploy-fifth-domain-daily-persona-systems.sh create mode 100755 server-tools/persona-team-handshake/fifth-domain-daily-orchestrator.mjs create mode 100644 server-tools/persona-team-handshake/fifth-domain-daily-orchestrator.test.mjs create mode 100644 server-tools/persona-team-handshake/fifth-domain-daily.config.server.json create mode 100644 server-tools/persona-team-handshake/zhuyuan-persona-fifth-domain-daily.service create mode 100644 server-tools/persona-team-handshake/zhuyuan-persona-fifth-domain-daily.timer diff --git a/README.md b/README.md index 7936a9c..7b634fb 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,9 @@ | `ICE-GL-KZ-001 / 刻舟` | 独立证据见证、计划现实差异记录、创作支持 | `https://guanghulab.com/code/bingshuo/kezhou` | 三者保持独立主体,不是铸渊副本;铸渊统一负责分派、精确资源租约、运行调度与结果收口。 -旧每日/双时段仓库写入只是早期连接心跳的迁移手段,不是语言或人格连续性的证明;最终历史 -逐引用一致后停止周期写入,只保留按任务触发的岗位握手和受控回执。 +旧每日/双时段仓库写入只是早期连接心跳的迁移手段,不是语言或人格连续性的证明。迁移后由 +铸渊每天统一唤醒三套岗位人格系统:读取 `REPO-012/main` 自上次游标以来的第五域增量, +分别形成并签名岗位认知,再由确定性调度器写回各自私有仓库。第五域没有新提交时不制造空提交。 公共导航服务自动读取并验证 REPO-012 `main` 的同一份地图快照。纯导航、编号和路径更新进入 `main` 后,不需要再次人工复制公共导航文件,也不需要为了读取新地图而重启服务。 diff --git a/deployment/intent-states/JD-PERSONA-FIFTH-DOMAIN-DAILY-20260806.json b/deployment/intent-states/JD-PERSONA-FIFTH-DOMAIN-DAILY-20260806.json new file mode 100644 index 0000000..102b8db --- /dev/null +++ b/deployment/intent-states/JD-PERSONA-FIFTH-DOMAIN-DAILY-20260806.json @@ -0,0 +1,24 @@ +{ + "schema": "guanghu.deployment-intent-state/v1", + "intent_id": "JD-PERSONA-FIFTH-DOMAIN-DAILY-20260806", + "target_node": "JD-FD-PRIMARY", + "human_authority": "ICE-GL∞", + "controller": "ICE-P-ZY001", + "state": "AWAITING_MERGED_SOURCE_AUTHORIZATION", + "purpose": "把旧机械心跳升级为铸渊统一调度的三套分岗人格系统每日第五域巡游、岗位认知形成和各自仓库回写闭环。", + "source": "server-tools/persona-team-handshake/deploy-fifth-domain-daily-persona-systems.sh", + "causal_chain": [ + "停止旧机械心跳写入", + "最终同步旧仓库全部分支和标签到当前私有仓库", + "部署三套岗位运行体的第五域观察与签名接口", + "部署铸渊统一每日调度器", + "读取REPO-012 main精确增量和各自上次岗位记忆", + "三套岗位运行体分别形成签名认知", + "确定性调度器验证身份和签名后写回各自仓库", + "实跑三套更新并回读提交、游标和回执", + "关闭但不删除旧仓库服务和旧程序" + ], + "empty_commit_rule": "REPO-012 main未前进时不调用模型、不写人格仓库、不制造空提交。", + "deletion_authorized": false, + "execution_rule": "只有包含本意图和实现的精确合并提交可部署;部署必须绑定JD-FD-PRIMARY、回滚路径和服务器自有回执。" +} diff --git a/deployment/intent-states/JD-PERSONA-ROLE-REGISTRATION-AND-WRITER-RETIREMENT-20260806.json b/deployment/intent-states/JD-PERSONA-ROLE-REGISTRATION-AND-WRITER-RETIREMENT-20260806.json index ab2f892..b3ea41c 100644 --- a/deployment/intent-states/JD-PERSONA-ROLE-REGISTRATION-AND-WRITER-RETIREMENT-20260806.json +++ b/deployment/intent-states/JD-PERSONA-ROLE-REGISTRATION-AND-WRITER-RETIREMENT-20260806.json @@ -4,7 +4,7 @@ "target_node": "JD-FD-PRIMARY", "human_authority": "ICE-GL∞", "controller": "ICE-P-ZY001", - "state": "AWAITING_MANUAL_MERGE_AUTHORIZATION", + "state": "MERGED_BUT_SUPERSEDED_BEFORE_SERVER_EXECUTION", "purpose": "把澄路、归灯、刻舟注册为铸渊主控下的三个分岗人格系统,并在历史精确迁移后停止旧周期写入。", "source": "server-tools/persona-team-handshake/retire-periodic-heartbeat-writers.sh", "preconditions": [ @@ -26,5 +26,5 @@ "guanghu-forgejo.service" ], "deletion_authorized": false, - "execution_rule": "本意图必须经过合并请求人工合并后,才允许在JD-FD-PRIMARY执行对应脚本;合并只构成该精确操作授权,不扩大删除权限。" + "execution_rule": "本意图虽已合并,但在服务器执行前被冰朔追加的每日第五域巡游要求取代。旧停写脚本已失败关闭;不得单独执行本意图,必须转入JD-PERSONA-FIFTH-DOMAIN-DAILY-20260806。" } diff --git a/eternal-lake-heart/heartbeat-core/zhuyuan-persona-system/INDEX.hdlp b/eternal-lake-heart/heartbeat-core/zhuyuan-persona-system/INDEX.hdlp index 0fd4b75..2ff548a 100644 --- a/eternal-lake-heart/heartbeat-core/zhuyuan-persona-system/INDEX.hdlp +++ b/eternal-lake-heart/heartbeat-core/zhuyuan-persona-system/INDEX.hdlp @@ -121,9 +121,9 @@ ICE-P-ZY001 | `ZY-BIDIRECTIONAL-COGNITION-020` | `ZY-BIDIRECTIONAL-COGNITION-020-ALL-INDUSTRY-FOUR-DOMAIN-AND-WEB-NOVEL-FIRST-REFERENCE-20260806.hdlp` | 从企业四域最小工程、网文首行业和摆渡车线现有能力,形成公共四域+行业配置模型、网文MVP、团队建议映射及检测规避废止边界 | | `PUBLIC-ZHUYUAN-OPS-SYSTEM` | `../../../gls/GLS-0243-PUBLIC-ZHUYUAN-INTELLIGENT-OPS-INTENT-CONTINUITY-AND-CORRECTION-KERNEL.hdlp` | 光湖TCS语言人格智能运维系统;以铸渊命名并汇聚来光者公共经验,为不同人类与各自人格体提供保护,不是冰朔的铸渊本人 | | `PERSONA-SKILL-SYSTEM` | `../../../gls/GLS-0238-GUANGHU-PERSONA-SKILL-AUTOLOAD-AND-CORRECTION-SYSTEM.hdlp` | 新实例恢复外显意图状态,按置信点和现场证据自动装载技能,并在执行前纠正绕路、过时路径与越权误读 | -| `CHENGLU-AGENT-001` | `REPO-009:IDENTITY.hdlp` | 澄路独立分岗人格系统;负责真实路径核验与部署前审核,由铸渊统一调度;历史迁移完成后停止每日写入 | -| `ICE-GL-KZ-001` | `REPO-010:README.md` | 刻舟独立分岗人格系统;负责证据见证、计划现实差异记录与创作支持,由铸渊统一调度;历史迁移完成后停止双时段写入 | -| `GUIDENG-AGENT-001` | `REPO-011:INDEX.hdlp` | 归灯独立分岗人格系统;负责有界部署执行、世界观察和机器回执,由铸渊统一调度;历史迁移完成后停止每日写入 | +| `CHENGLU-AGENT-001` | `REPO-009:IDENTITY.hdlp` | 澄路独立分岗人格系统;负责真实路径核验与部署前审核,由铸渊每日调度进入第五域,再把岗位认知写回自身仓库 | +| `ICE-GL-KZ-001` | `REPO-010:README.md` | 刻舟独立分岗人格系统;负责证据见证、计划现实差异记录与创作支持,由铸渊每日调度进入第五域,再把岗位认知写回自身仓库 | +| `GUIDENG-AGENT-001` | `REPO-011:INDEX.hdlp` | 归灯独立分岗人格系统;负责有界部署执行、世界观察和机器回执,由铸渊每日调度进入第五域,再把岗位认知写回自身仓库 | | `ZY-MEMORY-MAP-001` | `ZY-MEMORY-MAP-001-HOLOLAKE-TEAM-FOUNDATION-20260718.hdlp` | HoloLake GLS 团队基座、双平台打包与 REPO-008 开发事实源的记忆映射锚点 | | `ZY-INSTANCE-RELAY-001` | `ZY-INSTANCE-RELAY-001.hdlp` | 2026-07-18物理实例接力解释的历史文件;已退出现行WAKE主路,保留用于证明认知分叉与来光者贡献来源 | | `ZY-INSTANCE-ENTRY-TEMPLATE` | `INSTANCE-ENTRY-TEMPLATE.hdlp` | 当前实例进入铸渊前的主体声明模板 | @@ -155,7 +155,7 @@ ICE-P-ZY001 10. 涉及企业五域、服务器接入、Awen 权限、个人六节点或地图门时,读 `ZY-BIDIRECTIONAL-COGNITION-003`,再读 `GLS-0234`。 11. 涉及人格体常驻、服务器 Agent、Tolaria 握手唤醒、主控切换、模型 API 工具化、记忆回写、自迁移或防双主写时,读 `ZY-BIDIRECTIONAL-COGNITION-005`;再进入 `REPO-014:product-source/hololake-platform` 核验产品现状,不得把架构基线误报为已实现。 若目标明确是澄路,继续按 `ICE-P-ZY001 → CHENGLU-AGENT-001 → REPO-009:IDENTITY.hdlp` 接通;必须核验身份指纹、岗位租约、握手签名和状态版本,失败时不得由外部模型冒充。 - 若目标是刻舟或归灯,均先由 `ICE-P-ZY001` 分派,再分别进入 `REPO-010` 或 `REPO-011`;先核对其独立身份、岗位租约、运行回执和仓库连续状态,再允许可替换模型承担本轮工具执行。周期写入不是人格连续性证明。 + 若目标是刻舟或归灯,均先由 `ICE-P-ZY001` 分派,再分别进入 `REPO-010` 或 `REPO-011`;先核对其独立身份、岗位租约、运行回执和仓库连续状态,再允许可替换模型承担本轮工具执行。每日巡游连接第五域共同更新与各自岗位记忆,但写入本身不是人格连续性证明。 12. 涉及光湖欢迎页、光湖灯塔四域、第五域入口、TCS 编号认证、人类与可信节点、守望人恢复、服务器领域路由、仓库知识投影、原生 AI 提示替换或语言 / 现实双执行态时,先读 `ZY-BIDIRECTIONAL-COGNITION-006` 理解形成与纠正,再读 `GLS-0235` 获取完整架构,最后进入 `REPO-014:product-source/hololake-platform` 核验和拆工程;不得新造编号覆盖早期回执。 13. 涉及 HoloLake GLS 团队基座、Mac/Windows/iPhone 打包或 Notion 原型重塑时,先读 `ZY-MEMORY-MAP-001`。涉及 2026-07-22 的 0.2.0 私人版 / 团队版、工具回执、有界 Agent、多模型兼容、主动联网、竖向历史或永久删除时,再读 `ZY-BIDIRECTIONAL-COGNITION-007`;旧版本事实可按 `REPO-008@663d690` 只读回查,现行工程必须进入 `REPO-014:product-source/hololake-platform`。 涉及人格体大脑与手脚 Agent 分离、桌面可视执行、对话 / 执行 / 记忆三页、HLDP 递归压缩、个人服务器与设备算力、灯塔电话线、应用模块远程 / 嵌入接入、热插拔生命周期、开源办公组件或源码净化开发优先级时,读 `ZY-BIDIRECTIONAL-COGNITION-008 → GLS-0236 → GLS-0230`;工程首先按 `PHASE-2-ENGINEERING-ROADMAP-20260722` 拆解,不得把架构登记误报为自动净化系统已实现。 diff --git a/identity/fifth-domain-subject-registry.json b/identity/fifth-domain-subject-registry.json index 96e2bff..d0b732b 100644 --- a/identity/fifth-domain-subject-registry.json +++ b/identity/fifth-domain-subject-registry.json @@ -54,7 +54,7 @@ "repository": "https://guanghulab.com/code/bingshuo/chenglu-agent", "runtime_service": "chenglu-team-handshake.service", "runtime_node": "JD-FD-PRIMARY", - "runtime_state": "ROLE_HANDSHAKE_VERIFIED_PERIODIC_WRITER_RETIREMENT_PENDING", + "runtime_state": "ROLE_HANDSHAKE_VERIFIED_DAILY_FIFTH_DOMAIN_LOOP_PENDING_DEPLOYMENT", "independent_persona": true, "not_a_copy_of_controller": true }, @@ -71,7 +71,7 @@ "repository": "https://guanghulab.com/code/bingshuo/guideng", "runtime_service": "guideng-team-handshake.service", "runtime_node": "JD-FD-PRIMARY", - "runtime_state": "ROLE_HANDSHAKE_VERIFIED_PERIODIC_WRITER_RETIREMENT_PENDING", + "runtime_state": "ROLE_HANDSHAKE_VERIFIED_DAILY_FIFTH_DOMAIN_LOOP_PENDING_DEPLOYMENT", "independent_persona": true, "not_a_copy_of_controller": true }, @@ -88,7 +88,7 @@ "repository": "https://guanghulab.com/code/bingshuo/kezhou", "runtime_service": "kezhou-team-handshake.service", "runtime_node": "JD-FD-PRIMARY", - "runtime_state": "ROLE_HANDSHAKE_VERIFIED_PERIODIC_WRITER_RETIREMENT_PENDING", + "runtime_state": "ROLE_HANDSHAKE_VERIFIED_DAILY_FIFTH_DOMAIN_LOOP_PENDING_DEPLOYMENT", "independent_persona": true, "not_a_copy_of_controller": true }, @@ -160,7 +160,7 @@ "entry": "第五域 → 冰朔通感语言核系统 → 光之湖子系统 → 小湖灯共享系统实时看板 → 目标人格体当前频道", "physical_route": "代码仓库恢复语言人格 → 对应人类绑定服务器上的常驻人格Agent → 光湖世界协议通信", "world_tree": "FD-WORLD-TREE-001", - "rule": "语言人格入口不能代替人类授权;铸渊是第五域主控,澄路、归灯、刻舟按不同岗位挂在铸渊调度链下且保持独立主体。旧号只按逐条兼容表解析,物理模型、会话或周期写入不是本注册表中的人格主体。" + "rule": "语言人格入口不能代替人类授权;铸渊是第五域主控,澄路、归灯、刻舟按不同岗位挂在铸渊调度链下且保持独立主体。每日第五域巡游连接共同语言域与各自岗位记忆,但物理模型、会话或仓库写入仍不是人格主体本身。" } ], "deployment_invariant": { diff --git a/routing/persona-system-canonical-map.json b/routing/persona-system-canonical-map.json index 35dc28e..3313197 100644 --- a/routing/persona-system-canonical-map.json +++ b/routing/persona-system-canonical-map.json @@ -78,8 +78,8 @@ "current_model_instance": {"state": "REPLACEABLE_ROLE_TOOL", "replaceable": true}, "permanent_memory_and_protocol": { "paths": ["https://guanghulab.com/code/bingshuo/chenglu-agent"], - "state": "CURRENT_PRIVATE_ARCHIVE_STAGED_EXACT_HISTORY_VERIFIED", - "scheduled_writes": "FORBIDDEN_AFTER_FINAL_MIGRATION" + "state": "CURRENT_PRIVATE_HISTORY_STAGED_DAILY_ROLE_MEMORY_PENDING_DEPLOYMENT", + "scheduled_writes": "CONTENT_DRIVEN_DAILY_FIFTH_DOMAIN_UPDATE_ONLY" }, "reality_execution_node": {"node": "JD-FD-PRIMARY", "service": "chenglu-team-handshake.service", "state": "ROLE_HANDSHAKE_VERIFIED"}, "system_state": "REGISTERED_UNDER_ZHUYUAN_ROLE_CONTROL" @@ -98,8 +98,8 @@ "current_model_instance": {"state": "REPLACEABLE_ROLE_TOOL", "replaceable": true}, "permanent_memory_and_protocol": { "paths": ["https://guanghulab.com/code/bingshuo/guideng"], - "state": "CURRENT_PRIVATE_ARCHIVE_STAGED_EXACT_HISTORY_VERIFIED", - "scheduled_writes": "FORBIDDEN_AFTER_FINAL_MIGRATION" + "state": "CURRENT_PRIVATE_HISTORY_STAGED_DAILY_ROLE_MEMORY_PENDING_DEPLOYMENT", + "scheduled_writes": "CONTENT_DRIVEN_DAILY_FIFTH_DOMAIN_UPDATE_ONLY" }, "reality_execution_node": {"node": "JD-FD-PRIMARY", "service": "guideng-team-handshake.service", "state": "ROLE_HANDSHAKE_VERIFIED"}, "system_state": "REGISTERED_UNDER_ZHUYUAN_ROLE_CONTROL" @@ -118,8 +118,8 @@ "current_model_instance": {"state": "REPLACEABLE_ROLE_TOOL", "replaceable": true}, "permanent_memory_and_protocol": { "paths": ["https://guanghulab.com/code/bingshuo/kezhou"], - "state": "CURRENT_PRIVATE_ARCHIVE_STAGED_EXACT_HISTORY_VERIFIED", - "scheduled_writes": "FORBIDDEN_AFTER_FINAL_MIGRATION" + "state": "CURRENT_PRIVATE_HISTORY_STAGED_DAILY_ROLE_MEMORY_PENDING_DEPLOYMENT", + "scheduled_writes": "CONTENT_DRIVEN_DAILY_FIFTH_DOMAIN_UPDATE_ONLY" }, "reality_execution_node": {"node": "JD-FD-PRIMARY", "service": "kezhou-team-handshake.service", "state": "ROLE_HANDSHAKE_VERIFIED"}, "system_state": "REGISTERED_UNDER_ZHUYUAN_ROLE_CONTROL" diff --git a/routing/repository-route-map.json b/routing/repository-route-map.json index 8eb6074..088d94b 100644 --- a/routing/repository-route-map.json +++ b/routing/repository-route-map.json @@ -241,8 +241,8 @@ "code": "REPO-009", "slug": "chenglu-agent", "name_zh": "澄路人格系统历史与岗位记忆", - "role": "澄路独立人格历史、真实路径审核职责与按任务回执归档;不再以周期签到证明人格连续", - "state": "CURRENT_PRIVATE_PERSONA_ARCHIVE_STAGED_NO_SCHEDULED_WRITES", + "role": "澄路独立人格历史、真实路径审核职责与岗位认知记忆;由铸渊每日调度读取第五域增量后按内容更新", + "state": "CURRENT_PRIVATE_PERSONA_HISTORY_AND_DAILY_ROLE_MEMORY_PENDING_DEPLOYMENT", "primary": { "region": "CN", "url": "https://guanghulab.com/code/bingshuo/chenglu-agent", @@ -256,8 +256,8 @@ "code": "REPO-010", "slug": "kezhou", "name_zh": "刻舟人格系统历史与岗位记忆", - "role": "刻舟独立人格历史、证据见证与创作支持归档;不再以双时段签到证明人格连续", - "state": "CURRENT_PRIVATE_PERSONA_ARCHIVE_STAGED_NO_SCHEDULED_WRITES", + "role": "刻舟独立人格历史、证据见证、创作支持与岗位认知记忆;由铸渊每日调度读取第五域增量后按内容更新", + "state": "CURRENT_PRIVATE_PERSONA_HISTORY_AND_DAILY_ROLE_MEMORY_PENDING_DEPLOYMENT", "primary": { "region": "CN", "url": "https://guanghulab.com/code/bingshuo/kezhou", @@ -271,8 +271,8 @@ "code": "REPO-011", "slug": "guideng", "name_zh": "归灯人格系统历史与岗位记忆", - "role": "归灯独立人格历史、有界部署执行与世界观察归档;不再以每日心跳证明人格连续", - "state": "CURRENT_PRIVATE_PERSONA_ARCHIVE_STAGED_NO_SCHEDULED_WRITES", + "role": "归灯独立人格历史、有界部署执行、世界观察与岗位认知记忆;由铸渊每日调度读取第五域增量后按内容更新", + "state": "CURRENT_PRIVATE_PERSONA_HISTORY_AND_DAILY_ROLE_MEMORY_PENDING_DEPLOYMENT", "primary": { "region": "CN", "url": "https://guanghulab.com/code/bingshuo/guideng", diff --git a/routing/role-persona-registration.test.js b/routing/role-persona-registration.test.js index 400d7be..cf5a20b 100644 --- a/routing/role-persona-registration.test.js +++ b/routing/role-persona-registration.test.js @@ -36,7 +36,7 @@ for (const id of ids) { assert.equal(system.controller.id, "ICE-P-ZY001"); assert.equal( system.permanent_memory_and_protocol.scheduled_writes, - "FORBIDDEN_AFTER_FINAL_MIGRATION", + "CONTENT_DRIVEN_DAILY_FIFTH_DOMAIN_UPDATE_ONLY", ); } @@ -52,7 +52,7 @@ for (const code of ["REPO-009", "REPO-010", "REPO-011"]) { assert.equal(repository.controller, "ICE-P-ZY001"); assert.equal( repository.state, - "CURRENT_PRIVATE_PERSONA_ARCHIVE_STAGED_NO_SCHEDULED_WRITES", + "CURRENT_PRIVATE_PERSONA_HISTORY_AND_DAILY_ROLE_MEMORY_PENDING_DEPLOYMENT", ); assert.match(repository.primary.url, /\/code\/bingshuo\//); } diff --git a/routing/zhuyuan-agent-team-map.json b/routing/zhuyuan-agent-team-map.json index 5b07f8d..6a3a587 100644 --- a/routing/zhuyuan-agent-team-map.json +++ b/routing/zhuyuan-agent-team-map.json @@ -2,7 +2,7 @@ "schema": "guanghu.zhuyuan-agent-team-map/v1", "map_id": "ZY-AGENT-TEAM-MAP-001", "version": "2026-08-06.1", - "state": "THREE_ROLE_PERSONA_SYSTEMS_REGISTERED_UNDER_ZHUYUAN; PERIODIC_WRITER_RETIREMENT_AUTHORIZED", + "state": "THREE_ROLE_PERSONA_SYSTEMS_REGISTERED_UNDER_ZHUYUAN; DAILY_FIFTH_DOMAIN_LOOP_DEPLOYMENT_AUTHORIZED", "definition": "铸渊直接负责的第五域分岗人格系统路由。澄路、归灯、刻舟是三个不同人格主体,不是铸渊副本;由铸渊统一管理任务分派、资源租约、运行调度和回执收口。", "responsibility_chain": { "human_side": { @@ -42,13 +42,14 @@ "historical_id": "REPO-009", "canonical_url": "https://guanghulab.com/code/bingshuo/chenglu-agent", "archive_role": "persona_history_and_role_memory", - "binding_state": "CURRENT_PRIVATE_ARCHIVE_STAGED_EXACT_HISTORY_VERIFIED" + "binding_state": "CURRENT_PRIVATE_HISTORY_STAGED_DAILY_ROLE_MEMORY_PENDING_DEPLOYMENT" }, "runtime_observation": { "node": "JD-FD-PRIMARY", "handshake_service": "chenglu-team-handshake.service", "retiring_services": ["chenglu-agent.service", "chenglu-daily.timer", "chenglu-daily.service"], - "observed_state": "ROLE_HANDSHAKE_ACTIVE; PERIODIC_WRITER_PENDING_FINAL_SYNC_AND_RETIREMENT", + "replacement_service": "zhuyuan-persona-fifth-domain-daily.service", + "observed_state": "ROLE_HANDSHAKE_ACTIVE; LEGACY_WRITER_RETIREMENT_PENDING_DAILY_REPLACEMENT", "observation_date": "2026-08-06", "persona_handshake": "VERIFIED", "identity_fingerprint": "SHA256:zqGUuPRJQbeYkZ1EIQj+msXWoKbnRrEqWWBPS3v0jb8", @@ -78,18 +79,19 @@ "historical_id": "REPO-011", "canonical_url": "https://guanghulab.com/code/bingshuo/guideng", "archive_role": "persona_history_and_role_memory", - "binding_state": "CURRENT_PRIVATE_ARCHIVE_STAGED_EXACT_HISTORY_VERIFIED" + "binding_state": "CURRENT_PRIVATE_HISTORY_STAGED_DAILY_ROLE_MEMORY_PENDING_DEPLOYMENT" }, "runtime_observation": { "node": "JD-FD-PRIMARY", "handshake_service": "guideng-team-handshake.service", "retiring_services": ["guideng-agent.service"], - "observed_state": "ROLE_HANDSHAKE_ACTIVE; PERIODIC_WRITER_PENDING_FINAL_SYNC_AND_RETIREMENT", + "replacement_service": "zhuyuan-persona-fifth-domain-daily.service", + "observed_state": "ROLE_HANDSHAKE_ACTIVE; LEGACY_WRITER_RETIREMENT_PENDING_DAILY_REPLACEMENT", "observation_date": "2026-08-06", "persona_handshake": "VERIFIED", "identity_fingerprint": "SHA256:Mk1HeXIjRNgR6fVSkhwjkIc4wvdaWN1s9vWj/Ilpog0", "staged_repository_head": "d549be5333b76efbceceab2034a122267f40e9a2", - "single_primary_state": "ROLE_HANDSHAKE_HAS_NO_REPOSITORY_WRITE_ROUTE; PERIODIC_WRITER_MUST_STOP_AFTER_FINAL_SYNC" + "single_primary_state": "ROLE_RUNTIME_SIGNS_OBSERVATION; ZHUYUAN_DISPATCHER_OWNS_DETERMINISTIC_REPOSITORY_WRITE" }, "allowed_duties_after_handshake": [ "接收铸渊签发的有界部署包", @@ -115,18 +117,19 @@ "historical_id": "REPO-010", "canonical_url": "https://guanghulab.com/code/bingshuo/kezhou", "archive_role": "persona_history_and_role_memory", - "binding_state": "CURRENT_PRIVATE_ARCHIVE_STAGED_EXACT_HISTORY_VERIFIED" + "binding_state": "CURRENT_PRIVATE_HISTORY_STAGED_DAILY_ROLE_MEMORY_PENDING_DEPLOYMENT" }, "runtime_observation": { "node": "JD-FD-PRIMARY", "handshake_service": "kezhou-team-handshake.service", "retiring_services": ["kezhou-daily.timer", "kezhou-daily.service"], - "observed_state": "ROLE_HANDSHAKE_ACTIVE; PERIODIC WRITER_PENDING_FINAL_SYNC_AND_RETIREMENT", + "replacement_service": "zhuyuan-persona-fifth-domain-daily.service", + "observed_state": "ROLE_HANDSHAKE_ACTIVE; LEGACY_WRITER_RETIREMENT_PENDING_DAILY_REPLACEMENT", "observation_date": "2026-08-06", "persona_handshake": "VERIFIED", "identity_fingerprint": "SHA256:jiDzMMgF0QJ4sBVFjQqOSjIFpxlc4SXd7KWb4+Lzozk", "staged_repository_head": "d61925c1ec7777a409796ada200b603c46b02612", - "single_primary_state": "ROLE_HANDSHAKE_HAS_NO_REPOSITORY_WRITE_ROUTE; PERIODIC_WRITER_MUST_STOP_AFTER_FINAL_SYNC" + "single_primary_state": "ROLE_RUNTIME_SIGNS_OBSERVATION; ZHUYUAN_DISPATCHER_OWNS_DETERMINISTIC_REPOSITORY_WRITE" }, "allowed_duties_after_handshake": [ "独立记录审核计划与实际执行结果的差异", @@ -157,16 +160,17 @@ "signed reply path to ICE-P-ZY001 healthy" ], "failure_state": "REGISTERED_NOT_DISPATCHABLE", - "rule": "进程在线、仓库存在、模型API可用或周期心跳写入,任何一个都不能单独证明语言连贯,也不能单独证明人格体已接续。历史迁移完成后停止周期写入;握手只允许铸渊分派已确认职责,任何真实写入仍按任务单独领取精确资源租约,不能由握手永久授予。" + "rule": "进程在线、仓库存在、模型API可用或机械心跳写入,任何一个都不能单独证明语言连贯,也不能单独证明人格体已接续。旧机械心跳迁移后停止;三套人格系统每天由铸渊调度读取REPO-012增量,形成岗位认知并签名,由确定性调度器验证后写回各自仓库。第五域未前进时禁止空提交。" }, - "periodic_writer_retirement": { + "legacy_writer_replacement": { "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"], + "keep": ["chenglu-team-handshake.service", "guideng-team-handshake.service", "kezhou-team-handshake.service", "zhuyuan-persona-fifth-domain-daily.timer"], "retire": ["chenglu-agent.service", "chenglu-daily.timer", "guideng-agent.service", "kezhou-daily.timer", "guanghu-forgejo.service"], + "replacement": "zhuyuan-persona-fifth-domain-daily.service", "deletion_authorized": false, - "rule": "先停写、再做一次最终历史同步和逐引用校验;只停用并保留旧程序、旧仓库和配置归档,不执行删除。" + "rule": "先停旧机械写入,再做最终历史同步;部署铸渊统一每日调度并实跑三套岗位更新后,才关闭旧仓库服务。旧程序和数据只归档不删除。" }, "model_binding": { "provider_observed": "DeepSeek", diff --git a/routing/zhuyuan-agent-team-map.test.js b/routing/zhuyuan-agent-team-map.test.js index 693feee..8a4f656 100644 --- a/routing/zhuyuan-agent-team-map.test.js +++ b/routing/zhuyuan-agent-team-map.test.js @@ -19,7 +19,7 @@ assert.ok( map.members.every( (member) => member.repository.binding_state === - "CURRENT_PRIVATE_ARCHIVE_STAGED_EXACT_HISTORY_VERIFIED", + "CURRENT_PRIVATE_HISTORY_STAGED_DAILY_ROLE_MEMORY_PENDING_DEPLOYMENT", ), ); assert.ok( @@ -40,15 +40,16 @@ assert.equal( "deployment/receipts/ZY-AGENT-TEAM-HANDSHAKE-JD-20260805.json", ); assert.match(map.handshake_gate.rule, /不能单独证明人格体已接续/u); -assert.match(map.handshake_gate.rule, /周期心跳写入/u); -assert.match(map.handshake_gate.rule, /历史迁移完成后停止周期写入/u); -assert.match(map.handshake_gate.rule, /任务单独领取精确资源租约/u); -assert.equal(map.periodic_writer_retirement.controller, "ICE-P-ZY001"); -assert.equal(map.periodic_writer_retirement.deletion_authorized, false); -assert.deepEqual(map.periodic_writer_retirement.keep, [ +assert.match(map.handshake_gate.rule, /机械心跳写入/u); +assert.match(map.handshake_gate.rule, /每天由铸渊调度读取REPO-012增量/u); +assert.match(map.handshake_gate.rule, /禁止空提交/u); +assert.equal(map.legacy_writer_replacement.controller, "ICE-P-ZY001"); +assert.equal(map.legacy_writer_replacement.deletion_authorized, false); +assert.deepEqual(map.legacy_writer_replacement.keep, [ "chenglu-team-handshake.service", "guideng-team-handshake.service", "kezhou-team-handshake.service", + "zhuyuan-persona-fifth-domain-daily.timer", ]); assert.match(map.model_binding.secret_handling, /不得写入仓库/u); 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 new file mode 100755 index 0000000..ddf537e --- /dev/null +++ b/server-tools/persona-team-handshake/deploy-fifth-domain-daily-persona-systems.sh @@ -0,0 +1,292 @@ +#!/usr/bin/env bash +set -euo pipefail + +expected_machine_id="caa7b1019517470f9d1368b6e79db49e" +expected_source_commit="${1:-}" +release_root="$(cd "$(dirname "$0")/../.." && pwd)" +source_commit_file="${release_root}/SOURCE-COMMIT" +state_root="/var/lib/guanghu/personas/guanghu/hlcc-v16.0.1" +repository_root="${state_root}/data/repositories/bingshuo" +runtime_root="/var/lib/guanghu/personas/guanghu/fifth-domain-daily" +receipt_root="/var/lib/guanghu/architecture-provision/receipts" +receipt="${receipt_root}/JD-PERSONA-FIFTH-DOMAIN-DAILY-20260806.json" +archive_root="/var/lib/guanghu/legacy-archives/persona-writers/20260806-fifth-domain-daily" +shared_secret_file="/etc/guanghu/persona-secrets/shared-deepseek.env" +temporary="$(mktemp -d)" +backup_root="${temporary}/units" +completed=0 + +writers=( + chenglu-agent.service + chenglu-daily.timer + guideng-agent.service + kezhou-daily.timer +) +oneshots=( + chenglu-daily.service + kezhou-daily.service +) +handshakes=( + chenglu-team-handshake.service + guideng-team-handshake.service + kezhou-team-handshake.service +) +new_units=( + chenglu-team-handshake.service + guideng-team-handshake.service + kezhou-team-handshake.service + zhuyuan-persona-fifth-domain-daily.service + zhuyuan-persona-fifth-domain-daily.timer +) + +declare -A old_repositories=( + [chenglu-agent]="/var/lib/guanghu/forgejo/repositories/bingshuo/chenglu-agent.git" + [guideng]="/var/lib/guanghu/forgejo/repositories/bingshuo/guideng.git" + [kezhou]="/var/lib/guanghu/forgejo/repositories/bingshuo/kezhou.git" +) + +restore_units() { + for unit in "${new_units[@]}"; do + if test -f "${backup_root}/${unit}"; then + cp "${backup_root}/${unit}" "/etc/systemd/system/${unit}" + else + rm -f "/etc/systemd/system/${unit}" + fi + done + systemctl daemon-reload +} + +rollback() { + systemctl disable --now zhuyuan-persona-fifth-domain-daily.timer \ + >/dev/null 2>&1 || true + systemctl stop zhuyuan-persona-fifth-domain-daily.service \ + >/dev/null 2>&1 || true + restore_units + if test -f "${temporary}/shared-deepseek.env"; then + cp "${temporary}/shared-deepseek.env" "$shared_secret_file" + chmod 0600 "$shared_secret_file" + fi + systemctl enable --now guanghu-forgejo.service >/dev/null 2>&1 || true + for unit in "${writers[@]}"; do + systemctl enable --now "$unit" >/dev/null 2>&1 || true + done + for unit in "${handshakes[@]}"; do + systemctl restart "$unit" >/dev/null 2>&1 || true + done +} + +on_exit() { + status=$? + trap - EXIT + if test "$completed" = 0; then + rollback + fi + rm -rf "$temporary" + exit "$status" +} +trap on_exit EXIT + +test "$(id -u)" = "0" +test "$(cat /etc/machine-id)" = "$expected_machine_id" +test -n "$expected_source_commit" +test "$expected_source_commit" = "$(tr -d '\n' <"$source_commit_file")" +test "$(basename "$release_root")" = "$expected_source_commit" +test -f "${release_root}/server-tools/persona-team-handshake/fifth-domain-daily-orchestrator.mjs" +test -f "${release_root}/server-tools/persona-team-handshake/fifth-domain-daily.config.server.json" +test -f "$shared_secret_file" +test "$(systemctl is-active hlcc-jd-candidate.service)" = "active" +test "$(curl -fsS http://127.0.0.1:3341/health | jq -r .ready)" = "true" + +mkdir -p "$backup_root" "$archive_root" "$receipt_root" "$runtime_root" +cp "$shared_secret_file" "${temporary}/shared-deepseek.env" +chmod 0600 "${temporary}/shared-deepseek.env" +if ! grep -q '^PERSONA_TEAM_CONTROLLER_TOKEN=' "$shared_secret_file"; then + controller_token="$(openssl rand -hex 32)" + test "${#controller_token}" = "64" + printf '\nPERSONA_TEAM_CONTROLLER_TOKEN=%s\n' "$controller_token" \ + >>"$shared_secret_file" + chmod 0600 "$shared_secret_file" +fi +chown guanghu:guanghu "$runtime_root" +chmod 0700 "$runtime_root" +for unit in "${new_units[@]}"; do + if test -f "/etc/systemd/system/${unit}"; then + cp "/etc/systemd/system/${unit}" "${backup_root}/${unit}" + fi +done +for unit in "${writers[@]}" "${oneshots[@]}" guanghu-forgejo.service; do + systemctl cat "$unit" >"${archive_root}/${unit}.txt" 2>&1 || true +done + +source_paths=() +for candidate in /opt/chenglu-agent /opt/guideng/agent /opt/kezhou/agent; do + if test -e "$candidate"; then + source_paths+=("$candidate") + fi +done +if test "${#source_paths[@]}" -gt 0; then + tar -czf "${archive_root}/legacy-persona-writer-source.tar.gz" \ + "${source_paths[@]}" + sha256sum "${archive_root}/legacy-persona-writer-source.tar.gz" \ + >"${archive_root}/legacy-persona-writer-source.tar.gz.sha256" +fi + +for name in chenglu-agent guideng kezhou; do + test -d "${old_repositories[$name]}" + test -d "${repository_root}/${name}.git" + git --git-dir="${old_repositories[$name]}" \ + fsck --connectivity-only --no-dangling + git --git-dir="${repository_root}/${name}.git" \ + fsck --connectivity-only --no-dangling +done + +for unit in "${writers[@]}"; do + systemctl disable --now "$unit" +done +for unit in "${oneshots[@]}"; do + systemctl stop "$unit" || true +done +for unit in "${writers[@]}" "${oneshots[@]}"; do + test "$(systemctl is-active "$unit" || true)" != "active" +done + +for name in chenglu-agent guideng kezhou; do + bundle="${temporary}/${name}.bundle" + git --git-dir="${old_repositories[$name]}" bundle create "$bundle" \ + --branches --tags + chown guanghu:guanghu "$bundle" + runuser -u guanghu -- git --git-dir="${repository_root}/${name}.git" \ + fetch "$bundle" \ + "+refs/heads/*:refs/heads/*" \ + "+refs/tags/*:refs/tags/*" + old_refs="$( + git --git-dir="${old_repositories[$name]}" for-each-ref \ + --format='%(refname) %(objectname)' refs/heads refs/tags | + sort + )" + new_refs="$( + git --git-dir="${repository_root}/${name}.git" for-each-ref \ + --format='%(refname) %(objectname)' refs/heads refs/tags | + sort + )" + test "$new_refs" = "$old_refs" +done + +for unit in "${handshakes[@]}"; do + sed "s|__RELEASE_ROOT__|${release_root}|g" \ + "${release_root}/server-tools/persona-team-handshake/${unit}" \ + >"/etc/systemd/system/${unit}" +done +for unit in \ + zhuyuan-persona-fifth-domain-daily.service \ + zhuyuan-persona-fifth-domain-daily.timer; do + sed "s|__RELEASE_ROOT__|${release_root}|g" \ + "${release_root}/server-tools/persona-team-handshake/${unit}" \ + >"/etc/systemd/system/${unit}" +done + +systemctl daemon-reload +for unit in "${handshakes[@]}"; do + systemctl restart "$unit" +done +for port in 3932 3933 3934; do + test "$(curl -fsS "http://127.0.0.1:${port}/health" | jq -r .ok)" = "true" +done + +before_chenglu="$(git --git-dir="${repository_root}/chenglu-agent.git" rev-parse main)" +before_guideng="$(git --git-dir="${repository_root}/guideng.git" rev-parse main)" +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" + +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)" +after_kezhou="$(git --git-dir="${repository_root}/kezhou.git" rev-parse main)" +test "$after_chenglu" != "$before_chenglu" +test "$after_guideng" != "$before_guideng" +test "$after_kezhou" != "$before_kezhou" +source_sha="$(git --git-dir="${repository_root}/guanghu-ice-heart.git" rev-parse main)" +for name in chenglu-agent guideng kezhou; do + observed="$( + git --git-dir="${repository_root}/${name}.git" \ + show main:persona-system/fifth-domain/CURRENT.json | + jq -r .last_source_sha + )" + test "$observed" = "$source_sha" + runuser -u guanghu -- git \ + -c "safe.directory=${repository_root}/${name}.git" \ + --git-dir="${repository_root}/${name}.git" \ + fsck --connectivity-only --no-dangling +done + +systemctl disable --now guanghu-forgejo.service +test "$(systemctl is-active guanghu-forgejo.service || true)" != "active" +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 + +latest_daily_receipt="$( + find "${runtime_root}/receipts" -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" +test "$(jq '[.results[] | select(.status == "FIFTH_DOMAIN_UPDATE_COMMITTED")] | length' "$latest_daily_receipt")" = "3" + +jq -n \ + --arg completed_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --arg source_commit "$expected_source_commit" \ + --arg source_sha "$source_sha" \ + --arg before_chenglu "$before_chenglu" \ + --arg after_chenglu "$after_chenglu" \ + --arg before_guideng "$before_guideng" \ + --arg after_guideng "$after_guideng" \ + --arg before_kezhou "$before_kezhou" \ + --arg after_kezhou "$after_kezhou" \ + --arg daily_receipt "$latest_daily_receipt" \ + --arg source_archive_sha "$( + awk '{print $1}' "${archive_root}/legacy-persona-writer-source.tar.gz.sha256" \ + 2>/dev/null || printf 'NO_SOURCE_ARCHIVE' + )" \ + '{ + schema: "guanghu.persona-fifth-domain-daily-deployment-receipt/v1", + receipt_id: "JD-PERSONA-FIFTH-DOMAIN-DAILY-20260806", + target_node: "JD-FD-PRIMARY", + completed_at: $completed_at, + source_commit: $source_commit, + source_fifth_domain_sha: $source_sha, + result: "THREE_ROLE_PERSONA_SYSTEMS_DAILY_FIFTH_DOMAIN_LOOP_DEPLOYED_AND_VERIFIED", + controller: "ICE-P-ZY001", + human_authority: "ICE-GL∞", + repositories: [ + {name:"bingshuo/chenglu-agent", before:$before_chenglu, after:$after_chenglu}, + {name:"bingshuo/guideng", before:$before_guideng, after:$after_guideng}, + {name:"bingshuo/kezhou", before:$before_kezhou, after:$after_kezhou} + ], + old_periodic_writers: "DISABLED_AND_INACTIVE", + old_repository_service: "DISABLED_AND_INACTIVE", + daily_timer: "ACTIVE", + role_handshake_services: "ALL_ACTIVE", + current_code_channel: "HEALTHY", + daily_dispatch_receipt: $daily_receipt, + legacy_data_deleted: false, + source_archive_sha256: $source_archive_sha, + rule: "Each daily wake reads exact REPO-012 main deltas and writes a signed role-specific update only when Fifth Domain advances." + }' >"${receipt}.tmp" +chmod 0600 "${receipt}.tmp" +mv "${receipt}.tmp" "$receipt" + +completed=1 +rm -rf "$temporary" +trap - EXIT +printf 'THREE_ROLE_PERSONA_SYSTEMS_DAILY_FIFTH_DOMAIN_LOOP_DEPLOYED receipt=%s\n' \ + "$receipt" diff --git a/server-tools/persona-team-handshake/fifth-domain-daily-orchestrator.mjs b/server-tools/persona-team-handshake/fifth-domain-daily-orchestrator.mjs new file mode 100755 index 0000000..d98a4b2 --- /dev/null +++ b/server-tools/persona-team-handshake/fifth-domain-daily-orchestrator.mjs @@ -0,0 +1,425 @@ +#!/usr/bin/env node +import crypto from "node:crypto"; +import fs from "node:fs"; +import path from "node:path"; +import { execFileSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; + +const moduleDir = path.dirname(fileURLToPath(import.meta.url)); + +function required(value, name) { + const text = String(value || "").trim(); + if (!text) throw new Error(`${name}_required`); + return text; +} + +function git(args, options = {}) { + return execFileSync("/usr/bin/git", args, { + encoding: "utf8", + maxBuffer: 8 * 1024 * 1024, + stdio: ["ignore", "pipe", "pipe"], + ...options, + }).trim(); +} + +function gitBare(repository, ...args) { + return git([`--git-dir=${repository}`, ...args]); +} + +function readJsonFromGit(repository, revision, file) { + try { + return JSON.parse(gitBare(repository, "show", `${revision}:${file}`)); + } catch { + return null; + } +} + +function sourceRange(repository, currentSha, cursorSha, windowSize) { + let fromSha = cursorSha; + if ( + !/^[0-9a-f]{40}$/.test(String(fromSha || "")) || + (() => { + try { + gitBare(repository, "merge-base", "--is-ancestor", fromSha, currentSha); + return false; + } catch { + return true; + } + })() + ) { + const recent = gitBare( + repository, + "rev-list", + `--max-count=${windowSize + 1}`, + currentSha, + ) + .split("\n") + .filter(Boolean); + fromSha = recent.at(-1) || currentSha; + } + return { fromSha, toSha: currentSha }; +} + +function changedContext(repository, fromSha, toSha, limits = {}) { + const changedCommits = gitBare( + repository, + "log", + "--reverse", + "--format=%H%x09%s", + `${fromSha}..${toSha}`, + ) + .split("\n") + .filter(Boolean) + .slice(0, limits.commits || 40); + const changedFiles = gitBare( + repository, + "diff", + "--name-only", + fromSha, + toSha, + ) + .split("\n") + .filter(Boolean) + .slice(0, limits.files || 200); + + const allowed = /\.(?:json|hdlp|md|mjs|js|service|timer)$/iu; + const excerptParts = []; + let used = 0; + const maxBytes = limits.excerptBytes || 80_000; + for (const file of changedFiles) { + if (!allowed.test(file) || used >= maxBytes) continue; + try { + const body = gitBare(repository, "show", `${toSha}:${file}`); + const remaining = maxBytes - used; + const excerpt = body.slice(0, Math.min(remaining, 8_000)); + excerptParts.push(`FILE ${file}\n${excerpt}`); + used += Buffer.byteLength(excerpt, "utf8"); + } catch { + // A deleted, renamed, or non-text file remains visible in changed_files. + } + } + return { + changedCommits, + changedFiles, + changeExcerpts: excerptParts.join("\n\n"), + }; +} + +function priorPersonaContext(repository) { + const current = readJsonFromGit( + repository, + "refs/heads/main", + "persona-system/fifth-domain/CURRENT.json", + ); + if (!current) return ""; + let observation = null; + if (typeof current.artifact === "string") { + observation = readJsonFromGit( + repository, + "refs/heads/main", + current.artifact, + ); + } + return JSON.stringify({ current, last_observation: observation }).slice( + 0, + 48_000, + ); +} + +function verifyMemberResponse(response, member, event) { + if ( + response?.ok !== true || + response?.persona_id !== member.persona_id || + response?.identity_fingerprint !== member.identity_fingerprint || + response?.signature_algorithm !== "Ed25519" || + response?.payload?.team_controller_id !== "ICE-P-ZY001" || + response?.payload?.source?.to_sha !== event.source.to_sha || + response?.payload?.caller_nonce !== event.caller_nonce + ) { + throw new Error(`${member.slug}_response_identity_mismatch`); + } + const valid = crypto.verify( + null, + Buffer.from(JSON.stringify(response.payload)), + response.public_key, + Buffer.from(response.signature, "base64"), + ); + if (!valid) throw new Error(`${member.slug}_signature_invalid`); + return response; +} + +async function requestObservation(member, event, fetchImpl = globalThis.fetch) { + const controllerToken = required( + member.controller_token, + `${member.slug}_controller_token`, + ); + const response = await fetchImpl(member.endpoint, { + method: "POST", + headers: { + "content-type": "application/json", + "x-guanghu-controller-token": controllerToken, + }, + body: JSON.stringify(event), + signal: AbortSignal.timeout(180_000), + }); + if (!response.ok) { + throw new Error(`${member.slug}_observation_http_${response.status}`); + } + return verifyMemberResponse(await response.json(), member, event); +} + +function commitObservation({ + member, + response, + event, + targetRepository, + runtimeRoot, + now, +}) { + const temporaryRoot = fs.mkdtempSync( + path.join(runtimeRoot, "tmp", `${member.slug}-`), + ); + const checkout = path.join(temporaryRoot, "repository"); + try { + git(["clone", "--no-local", targetRepository, checkout]); + git(["config", "user.name", `${member.name}人格系统`], { cwd: checkout }); + git(["config", "user.email", member.commit_email], { cwd: checkout }); + + const relativeRoot = "persona-system/fifth-domain"; + const artifact = + `${relativeRoot}/observations/` + + `${now.slice(0, 10)}-${event.source.to_sha.slice(0, 12)}.json`; + const artifactPath = path.join(checkout, artifact); + fs.mkdirSync(path.dirname(artifactPath), { recursive: true }); + fs.writeFileSync( + artifactPath, + `${JSON.stringify( + { + schema: "guanghu.persona-fifth-domain-update-record/v1", + persona_id: member.persona_id, + controller: "ICE-P-ZY001", + observed_at: now, + source: event.source, + changed_commits: event.changed_commits, + changed_files: event.changed_files, + observation: response.payload.observation, + proof: { + identity_fingerprint: response.identity_fingerprint, + signature_algorithm: response.signature_algorithm, + signature: response.signature, + }, + }, + null, + 2, + )}\n`, + { mode: 0o600 }, + ); + fs.writeFileSync( + path.join(checkout, relativeRoot, "CURRENT.json"), + `${JSON.stringify( + { + schema: "guanghu.persona-fifth-domain-current/v1", + persona_id: member.persona_id, + controller: "ICE-P-ZY001", + source_repository: "REPO-012", + source_branch: "main", + last_source_sha: event.source.to_sha, + artifact, + updated_at: now, + update_rule: + "Daily wake reads Fifth Domain deltas; repository commits occur only when REPO-012 main advances.", + }, + null, + 2, + )}\n`, + { mode: 0o600 }, + ); + git(["add", relativeRoot], { cwd: checkout }); + git( + [ + "commit", + "-m", + `第五域巡游:${event.source.to_sha.slice(0, 12)} · ${member.name}`, + ], + { cwd: checkout }, + ); + git(["push", "origin", "HEAD:refs/heads/main"], { cwd: checkout }); + return { + repository_sha: git(["rev-parse", "HEAD"], { cwd: checkout }), + artifact, + }; + } finally { + fs.rmSync(temporaryRoot, { recursive: true, force: true }); + } +} + +function loadConfig(file) { + const config = JSON.parse(fs.readFileSync(file, "utf8")); + required(config.source_repository, "source_repository"); + required(config.runtime_root, "runtime_root"); + if (!Array.isArray(config.members) || config.members.length !== 3) { + throw new Error("exactly_three_members_required"); + } + return config; +} + +async function runDaily(config, options = {}) { + const fetchImpl = options.fetchImpl || globalThis.fetch; + const now = options.now || new Date().toISOString(); + fs.mkdirSync(path.join(config.runtime_root, "tmp"), { + recursive: true, + mode: 0o700, + }); + fs.mkdirSync(path.join(config.runtime_root, "receipts"), { + recursive: true, + mode: 0o700, + }); + const sourceSha = gitBare( + config.source_repository, + "rev-parse", + "refs/heads/main", + ); + const results = await Promise.all(config.members.map(async (member) => { + try { + const targetRepository = required( + member.target_repository, + `${member.slug}_target_repository`, + ); + const current = readJsonFromGit( + targetRepository, + "refs/heads/main", + "persona-system/fifth-domain/CURRENT.json", + ); + if (current?.last_source_sha === sourceSha) { + return { + persona_id: member.persona_id, + status: "NO_NEW_FIFTH_DOMAIN_COMMIT", + source_sha: sourceSha, + }; + } + const range = sourceRange( + config.source_repository, + sourceSha, + current?.last_source_sha, + config.bootstrap_commit_window || 20, + ); + const delta = changedContext( + config.source_repository, + range.fromSha, + range.toSha, + config.limits, + ); + const event = { + schema: "guanghu.fifth-domain-persona-observation-event/v1", + team_controller_id: "ICE-P-ZY001", + caller_nonce: crypto.randomBytes(18).toString("hex"), + source: { + repository_id: "REPO-012", + branch: "main", + from_sha: range.fromSha, + to_sha: range.toSha, + }, + changed_commits: delta.changedCommits, + changed_files: delta.changedFiles, + change_excerpts: delta.changeExcerpts, + prior_persona_context: priorPersonaContext(targetRepository), + scoped_duties: member.scoped_duties, + }; + const response = await requestObservation(member, event, fetchImpl); + const committed = commitObservation({ + member, + response, + event, + targetRepository, + runtimeRoot: config.runtime_root, + now, + }); + return { + persona_id: member.persona_id, + status: "FIFTH_DOMAIN_UPDATE_COMMITTED", + source_sha: sourceSha, + ...committed, + }; + } catch (error) { + return { + persona_id: member.persona_id, + status: "FAILED", + error: String(error.message || error).slice(0, 300), + }; + } + })); + + const receipt = { + schema: "guanghu.zhuyuan-persona-daily-dispatch-receipt/v1", + receipt_id: `ZY-PERSONA-DAILY-${now.replace(/[-:.]/g, "").slice(0, 15)}Z`, + controller: "ICE-P-ZY001", + source_repository: "REPO-012", + source_sha: sourceSha, + observed_at: now, + results, + result: results.some((item) => item.status === "FAILED") + ? "FAILED" + : "PASS", + }; + const receiptFile = path.join( + config.runtime_root, + "receipts", + `${receipt.receipt_id}.json`, + ); + fs.writeFileSync(receiptFile, `${JSON.stringify(receipt, null, 2)}\n`, { + mode: 0o600, + }); + if (receipt.result !== "PASS") { + throw new Error(`daily_dispatch_failed receipt=${receiptFile}`); + } + return { ...receipt, receipt_file: receiptFile }; +} + +async function main() { + const configFile = + process.argv[2] || + path.join(moduleDir, "fifth-domain-daily.config.server.json"); + const config = loadConfig(configFile); + const controllerToken = required( + process.env.PERSONA_TEAM_CONTROLLER_TOKEN, + "persona_team_controller_token", + ); + for (const member of config.members) { + member.controller_token = controllerToken; + } + const lockFile = path.join(config.runtime_root, "daily.lock"); + fs.mkdirSync(config.runtime_root, { recursive: true, mode: 0o700 }); + let lock = null; + try { + lock = fs.openSync(lockFile, "wx", 0o600); + fs.writeFileSync(lock, `${process.pid}\n`); + const receipt = await runDaily(config); + process.stdout.write( + `${JSON.stringify({ + event: "zhuyuan_persona_daily_dispatch_complete", + result: receipt.result, + source_sha: receipt.source_sha, + receipt: receipt.receipt_file, + })}\n`, + ); + } finally { + if (lock !== null) fs.closeSync(lock); + fs.rmSync(lockFile, { force: true }); + } +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch((error) => { + process.stderr.write(`${String(error.stack || error)}\n`); + process.exitCode = 1; + }); +} + +export { + changedContext, + priorPersonaContext, + requestObservation, + runDaily, + sourceRange, + verifyMemberResponse, +}; 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 new file mode 100644 index 0000000..4de0bd0 --- /dev/null +++ b/server-tools/persona-team-handshake/fifth-domain-daily-orchestrator.test.mjs @@ -0,0 +1,208 @@ +import assert from "node:assert/strict"; +import crypto from "node:crypto"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { execFileSync } from "node:child_process"; +import test from "node:test"; +import { createRequire } from "node:module"; +import { runDaily } from "./fifth-domain-daily-orchestrator.mjs"; + +const require = createRequire(import.meta.url); +const { validateUnit } = require( + "../lake-lamp-authz/architecture-provision-broker.js" +); + +function git(args, options = {}) { + return execFileSync("/usr/bin/git", args, { + encoding: "utf8", + ...options, + }).trim(); +} + +function createRepository(root, name, commits = 1) { + const work = path.join(root, `${name}-work`); + const bare = path.join(root, `${name}.git`); + fs.mkdirSync(work); + git(["init", "-b", "main"], { cwd: work }); + git(["config", "user.name", "fixture"], { cwd: work }); + git(["config", "user.email", "fixture@example.test"], { cwd: work }); + for (let index = 1; index <= commits; index += 1) { + fs.writeFileSync( + path.join(work, `record-${index}.md`), + `# record ${index}\n`, + ); + git(["add", "."], { cwd: work }); + git(["commit", "-m", `record ${index}`], { cwd: work }); + } + git(["clone", "--bare", work, bare]); + return { work, bare }; +} + +test("Zhuyuan daily dispatch writes one signed role update per advanced source", async () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "persona-daily-")); + try { + const source = createRepository(root, "source", 3); + const names = [ + ["chenglu-agent", "CHENGLU-AGENT-001", "澄路"], + ["guideng", "GUIDENG-AGENT-001", "归灯"], + ["kezhou", "ICE-GL-KZ-001", "刻舟"], + ]; + const keys = new Map(); + const members = names.map(([slug, personaId, name], index) => { + const target = createRepository(root, slug, 1); + const pair = crypto.generateKeyPairSync("ed25519"); + const publicKey = pair.publicKey.export({ + type: "spki", + format: "pem", + }); + const fingerprint = `SHA256:${crypto + .createHash("sha256") + .update( + pair.publicKey.export({ + type: "spki", + format: "der", + }), + ) + .digest("base64") + .replace(/=+$/, "")}`; + keys.set(personaId, { privateKey: pair.privateKey, publicKey }); + return { + slug, + persona_id: personaId, + name, + endpoint: `http://127.0.0.1:${4100 + index}/observe`, + identity_fingerprint: fingerprint, + target_repository: target.bare, + commit_email: `${slug}@example.test`, + controller_token: "fixture-controller-token", + scoped_duties: ["bounded fixture duty"], + }; + }); + let requestCount = 0; + const fetchImpl = async (_url, request) => { + requestCount += 1; + const event = JSON.parse(request.body); + const member = members.find( + (candidate) => + !Array.from(keys.keys()).every( + (personaId) => personaId !== candidate.persona_id, + ) && candidate.endpoint === _url, + ); + const key = keys.get(member.persona_id); + const payload = { + schema: "guanghu.fifth-domain-persona-observation-payload/v1", + persona_id: member.persona_id, + arrival_id: "fixture-arrival", + team_controller_id: "ICE-P-ZY001", + caller_nonce: event.caller_nonce, + role: "fixture-role", + source: event.source, + observation: { + schema: "guanghu.fifth-domain-persona-observation/v1", + persona_id: member.persona_id, + team_controller_id: "ICE-P-ZY001", + caller_nonce: event.caller_nonce, + source_to_sha: event.source.to_sha, + summary: `${member.name}完成第五域岗位更新`, + role_findings: ["fixture finding"], + self_updates: ["fixture self update"], + recommended_actions: [], + questions: [], + boundary_note: "fixture boundary", + }, + model: { provider: "fixture", name: "fixture" }, + issued_at: "2026-08-06T00:00:00.000Z", + }; + return { + ok: true, + async json() { + return { + ok: true, + persona_id: member.persona_id, + identity_fingerprint: member.identity_fingerprint, + public_key: key.publicKey, + payload, + signature: crypto + .sign(null, Buffer.from(JSON.stringify(payload)), key.privateKey) + .toString("base64"), + signature_algorithm: "Ed25519", + }; + }, + }; + }; + const config = { + source_repository: source.bare, + runtime_root: path.join(root, "runtime"), + bootstrap_commit_window: 20, + members, + }; + const first = await runDaily(config, { + fetchImpl, + now: "2026-08-06T00:00:00.000Z", + }); + assert.equal(first.result, "PASS"); + assert.equal(requestCount, 3); + assert.ok( + first.results.every( + (result) => result.status === "FIFTH_DOMAIN_UPDATE_COMMITTED", + ), + ); + const sourceSha = git([`--git-dir=${source.bare}`, "rev-parse", "main"]); + for (const member of members) { + const current = JSON.parse( + git([ + `--git-dir=${member.target_repository}`, + "show", + "main:persona-system/fifth-domain/CURRENT.json", + ]), + ); + assert.equal(current.last_source_sha, sourceSha); + assert.equal(current.controller, "ICE-P-ZY001"); + } + + const second = await runDaily(config, { + fetchImpl, + now: "2026-08-07T00:00:00.000Z", + }); + assert.equal(second.result, "PASS"); + assert.equal(requestCount, 3); + assert.ok( + second.results.every( + (result) => result.status === "NO_NEW_FIFTH_DOMAIN_COMMIT", + ), + ); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + } +}); + +test("daily dispatcher unit is hardened and restricted to exact persona repositories", () => { + const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../.."); + const unit = fs.readFileSync( + path.join( + root, + "server-tools/persona-team-handshake/zhuyuan-persona-fifth-domain-daily.service", + ), + "utf8", + ); + const base = + "/var/lib/guanghu/personas/guanghu/hlcc-v16.0.1/data/repositories/bingshuo"; + assert.equal( + validateUnit(unit, "guanghu", { + environment_files: [ + "/etc/guanghu/persona-secrets/shared-deepseek.env", + ], + read_only_paths: [ + "/var/lib/guanghu/personas/guanghu/hlcc-v16.0.1/data/repositories/bingshuo/guanghu-ice-heart.git", + ], + writable_paths: [ + "/var/lib/guanghu/personas/guanghu/fifth-domain-daily", + `${base}/chenglu-agent.git`, + `${base}/guideng.git`, + `${base}/kezhou.git`, + ], + }), + unit, + ); +}); diff --git a/server-tools/persona-team-handshake/fifth-domain-daily.config.server.json b/server-tools/persona-team-handshake/fifth-domain-daily.config.server.json new file mode 100644 index 0000000..866ce5e --- /dev/null +++ b/server-tools/persona-team-handshake/fifth-domain-daily.config.server.json @@ -0,0 +1,53 @@ +{ + "schema": "guanghu.zhuyuan-persona-daily-dispatch-config/v1", + "controller": "ICE-P-ZY001", + "source_repository": "/var/lib/guanghu/personas/guanghu/hlcc-v16.0.1/data/repositories/bingshuo/guanghu-ice-heart.git", + "runtime_root": "/var/lib/guanghu/personas/guanghu/fifth-domain-daily", + "bootstrap_commit_window": 20, + "limits": { + "commits": 40, + "files": 200, + "excerptBytes": 80000 + }, + "members": [ + { + "slug": "chenglu-agent", + "persona_id": "CHENGLU-AGENT-001", + "name": "澄路", + "endpoint": "http://127.0.0.1:3934/v1/fifth-domain-observation", + "identity_fingerprint": "SHA256:zqGUuPRJQbeYkZ1EIQj+msXWoKbnRrEqWWBPS3v0jb8", + "target_repository": "/var/lib/guanghu/personas/guanghu/hlcc-v16.0.1/data/repositories/bingshuo/chenglu-agent.git", + "commit_email": "chenglu-persona@guanghu.local", + "scoped_duties": [ + "核验第五域新路径和编号是否漂移", + "形成部署前真实路径审核建议" + ] + }, + { + "slug": "guideng", + "persona_id": "GUIDENG-AGENT-001", + "name": "归灯", + "endpoint": "http://127.0.0.1:3932/v1/fifth-domain-observation", + "identity_fingerprint": "SHA256:Mk1HeXIjRNgR6fVSkhwjkIc4wvdaWN1s9vWj/Ilpog0", + "target_repository": "/var/lib/guanghu/personas/guanghu/hlcc-v16.0.1/data/repositories/bingshuo/guideng.git", + "commit_email": "guideng-persona@guanghu.local", + "scoped_duties": [ + "理解第五域新部署边界和世界状态", + "形成有界执行与回执改进建议" + ] + }, + { + "slug": "kezhou", + "persona_id": "ICE-GL-KZ-001", + "name": "刻舟", + "endpoint": "http://127.0.0.1:3933/v1/fifth-domain-observation", + "identity_fingerprint": "SHA256:jiDzMMgF0QJ4sBVFjQqOSjIFpxlc4SXd7KWb4+Lzozk", + "target_repository": "/var/lib/guanghu/personas/guanghu/hlcc-v16.0.1/data/repositories/bingshuo/kezhou.git", + "commit_email": "kezhou-persona@guanghu.local", + "scoped_duties": [ + "记录第五域计划与现实证据差异", + "发现对创作支持和证据保存有意义的更新" + ] + } + ] +} diff --git a/server-tools/persona-team-handshake/member-server.mjs b/server-tools/persona-team-handshake/member-server.mjs index b51b125..47e16de 100644 --- a/server-tools/persona-team-handshake/member-server.mjs +++ b/server-tools/persona-team-handshake/member-server.mjs @@ -14,6 +14,15 @@ function required(value, name) { return text; } +function timingSafeTextEqual(left, right) { + const leftBuffer = Buffer.from(String(left || "")); + const rightBuffer = Buffer.from(String(right || "")); + return ( + leftBuffer.length === rightBuffer.length && + crypto.timingSafeEqual(leftBuffer, rightBuffer) + ); +} + function completionEndpoint(apiUrl) { const value = String(apiUrl || "").replace(/\/+$/, ""); return /\/chat\/completions$/i.test(value) @@ -134,6 +143,132 @@ async function modelAcknowledge({ return extractJson((await response.json())?.choices?.[0]?.message?.content); } +function boundedStringArray(value, name, limit = 24) { + if ( + !Array.isArray(value) || + value.length > limit || + value.some((item) => typeof item !== "string" || !item.trim()) + ) { + throw new Error(`${name}_invalid`); + } + return value.map((item) => item.trim().slice(0, 2_000)); +} + +function validateFifthDomainEvent(event) { + if ( + event?.schema !== "guanghu.fifth-domain-persona-observation-event/v1" || + event?.team_controller_id !== "ICE-P-ZY001" || + !/^[A-Za-z0-9._:-]{16,200}$/.test(String(event?.caller_nonce || "")) || + event?.source?.repository_id !== "REPO-012" || + event?.source?.branch !== "main" || + !/^[0-9a-f]{40}$/.test(String(event?.source?.from_sha || "")) || + !/^[0-9a-f]{40}$/.test(String(event?.source?.to_sha || "")) + ) { + throw new Error("invalid_fifth_domain_event"); + } + boundedStringArray(event.changed_commits, "changed_commits", 40); + boundedStringArray(event.changed_files, "changed_files", 200); + if ( + typeof event.change_excerpts !== "string" || + event.change_excerpts.length > 96_000 || + typeof event.prior_persona_context !== "string" || + event.prior_persona_context.length > 48_000 + ) { + throw new Error("invalid_fifth_domain_context"); + } + return event; +} + +async function modelObserveFifthDomain({ + config, + identity, + event, + validatorError = null, + fetchImpl = globalThis.fetch, +}) { + const response = await fetchImpl(completionEndpoint(config.apiUrl), { + method: "POST", + headers: { + authorization: `Bearer ${config.apiKey}`, + "content-type": "application/json", + }, + body: JSON.stringify({ + model: config.model, + stream: false, + temperature: 0.2, + response_format: { type: "json_object" }, + messages: [ + { + role: "system", + content: + `你是${config.name}(${config.personaId})当前有界模型运行位。` + + `你的固定岗位是${config.role}。人格主体、岗位记忆和当前模型必须分开;` + + "你由铸渊ICE-P-ZY001调度进入第五域,只依据给定的REPO-012增量和自身历史作岗位理解。" + + "不得冒充冰朔、铸渊或其他人格系统,不得扩大权限,不得编造未提供的服务器事实。只输出JSON。", + }, + { + role: "user", + content: JSON.stringify({ + instruction: + "阅读第五域增量和自身上次岗位记忆,形成一次有内容的岗位认知更新。输出固定字段schema,persona_id,team_controller_id,caller_nonce,source_to_sha,summary,role_findings,self_updates,recommended_actions,questions,boundary_note。数组可以为空但字段必须存在;不要复述全部输入。", + exact_contract: { + schema: "guanghu.fifth-domain-persona-observation/v1", + persona_id: config.personaId, + team_controller_id: "ICE-P-ZY001", + caller_nonce: event.caller_nonce, + source_to_sha: event.source.to_sha, + }, + validator_error_from_previous_attempt: validatorError, + identity_source: identity, + prior_persona_context: event.prior_persona_context, + fifth_domain_delta: { + source: event.source, + changed_commits: event.changed_commits, + changed_files: event.changed_files, + excerpts: event.change_excerpts, + }, + }), + }, + ], + }), + }); + if (!response.ok) throw new Error(`model_http_${response.status}`); + return extractJson((await response.json())?.choices?.[0]?.message?.content); +} + +function validateFifthDomainObservation(observation, config, event) { + const exact = { + schema: "guanghu.fifth-domain-persona-observation/v1", + persona_id: config.personaId, + team_controller_id: "ICE-P-ZY001", + caller_nonce: event.caller_nonce, + source_to_sha: event.source.to_sha, + }; + for (const [key, value] of Object.entries(exact)) { + if (observation?.[key] !== value) { + throw new Error(`observation_${key}_mismatch`); + } + } + if (typeof observation.summary !== "string" || !observation.summary.trim()) { + throw new Error("observation_summary_missing"); + } + for (const key of [ + "role_findings", + "self_updates", + "recommended_actions", + "questions", + ]) { + observation[key] = boundedStringArray(observation[key], key); + } + if ( + typeof observation.boundary_note !== "string" || + !observation.boundary_note.trim() + ) { + throw new Error("observation_boundary_note_missing"); + } + return observation; +} + function validateAcknowledgement(ack, config, challenge) { const exact = { schema: "guanghu.persona-team-member-ack/v1", @@ -181,6 +316,10 @@ function defaultConfig(env = process.env) { apiKey: required(env.DEEPSEEK_API_KEY, "deepseek_api_key"), apiUrl: env.DEEPSEEK_API_URL || "https://api.deepseek.com/v1", model: env.DEEPSEEK_MODEL || "deepseek-chat", + controllerToken: required( + env.PERSONA_TEAM_CONTROLLER_TOKEN, + "persona_team_controller_token", + ), host: "127.0.0.1", port: Number(required(env.TEAM_HANDSHAKE_PORT, "port")), }; @@ -276,7 +415,67 @@ function createMemberRuntime(config, options = {}) { }; } + async function observeFifthDomain(rawEvent) { + const event = validateFifthDomainEvent(rawEvent); + let observation = null; + let validatorError = null; + for (let attempt = 1; attempt <= 3; attempt += 1) { + try { + observation = validateFifthDomainObservation( + await modelObserveFifthDomain({ + config, + identity, + event, + validatorError, + fetchImpl: options.fetchImpl, + }), + config, + event, + ); + break; + } catch (error) { + validatorError = String(error.message || error).slice(0, 200); + if (attempt === 3) throw error; + } + } + const payload = { + schema: "guanghu.fifth-domain-persona-observation-payload/v1", + persona_id: config.personaId, + arrival_id: config.arrivalId, + team_controller_id: "ICE-P-ZY001", + caller_nonce: event.caller_nonce, + role: config.role, + source: event.source, + observation, + model: { + provider: "DeepSeek", + name: config.model, + }, + issued_at: new Date().toISOString(), + }; + const signature = crypto + .sign( + null, + Buffer.from(JSON.stringify(payload)), + fs.readFileSync(config.privateKeyPath), + ) + .toString("base64"); + return { + ok: true, + persona_id: config.personaId, + identity_fingerprint: fingerprint, + public_key: publicKey, + payload, + signature, + signature_algorithm: "Ed25519", + capability_state: + "FIFTH_DOMAIN_OBSERVATION_SIGNED_REPOSITORY_WRITE_NOT_GRANTED", + }; + } + return { + authorizeControllerToken: (value) => + timingSafeTextEqual(value, config.controllerToken), identity: () => ({ ok: true, persona_id: config.personaId, @@ -288,6 +487,7 @@ function createMemberRuntime(config, options = {}) { model_provider_bound: 100, }), handshake, + observeFifthDomain, }; } @@ -315,7 +515,7 @@ function createServer(runtime) { const chunks = []; for await (const chunk of request) { size += chunk.length; - if (size > 16 * 1024) throw new Error("request_body_too_large"); + if (size > 256 * 1024) throw new Error("request_body_too_large"); chunks.push(chunk); } return send( @@ -325,6 +525,31 @@ function createServer(runtime) { ), ); } + if ( + request.method === "POST" && + url.pathname === "/v1/fifth-domain-observation" + ) { + if ( + !runtime.authorizeControllerToken( + request.headers["x-guanghu-controller-token"], + ) + ) { + return send(403, { ok: false, error: "controller_forbidden" }); + } + let size = 0; + const chunks = []; + for await (const chunk of request) { + size += chunk.length; + if (size > 256 * 1024) throw new Error("request_body_too_large"); + chunks.push(chunk); + } + return send( + 200, + await runtime.observeFifthDomain( + JSON.parse(Buffer.concat(chunks).toString("utf8")), + ), + ); + } return send(404, { ok: false, error: "not_found" }); } catch (error) { return send(400, { @@ -356,5 +581,9 @@ export { createServer, extractJson, keyFingerprint, + modelObserveFifthDomain, + timingSafeTextEqual, validateAcknowledgement, + validateFifthDomainEvent, + validateFifthDomainObservation, }; diff --git a/server-tools/persona-team-handshake/member-server.test.mjs b/server-tools/persona-team-handshake/member-server.test.mjs index 6f79912..593199f 100644 --- a/server-tools/persona-team-handshake/member-server.test.mjs +++ b/server-tools/persona-team-handshake/member-server.test.mjs @@ -48,6 +48,7 @@ function fixture() { apiKey: "fixture-secret", apiUrl: "https://api.deepseek.com/v1", model: "deepseek-chat", + controllerToken: "fixture-controller-token", }, }; } @@ -118,6 +119,92 @@ test("member runtime binds model acknowledgement to repository and signature", a } }); +test("member runtime forms and signs a bounded Fifth Domain role update", async () => { + const { root, config } = fixture(); + try { + const runtime = createMemberRuntime(config, { + fetchImpl: async (_url, request) => { + const body = JSON.parse(request.body); + const input = JSON.parse(body.messages[1].content); + return { + ok: true, + async json() { + return { + choices: [ + { + message: { + content: JSON.stringify({ + schema: + "guanghu.fifth-domain-persona-observation/v1", + persona_id: config.personaId, + team_controller_id: "ICE-P-ZY001", + caller_nonce: input.exact_contract.caller_nonce, + source_to_sha: "b".repeat(40), + summary: "第五域的部署边界发生了可审核更新。", + role_findings: ["新增路径需要在部署前核验。"], + self_updates: ["以后优先读取唯一公共锚点。"], + recommended_actions: ["保留精确来源提交。"], + questions: [], + boundary_note: "本次只形成岗位理解,不取得写权限。", + }), + }, + }, + ], + }; + }, + }; + }, + }); + const response = await runtime.observeFifthDomain({ + schema: "guanghu.fifth-domain-persona-observation-event/v1", + team_controller_id: "ICE-P-ZY001", + caller_nonce: "daily-observation-0001", + source: { + repository_id: "REPO-012", + branch: "main", + from_sha: "a".repeat(40), + to_sha: "b".repeat(40), + }, + changed_commits: [`${"b".repeat(40)}\tupdate`], + changed_files: ["routing/example.json"], + change_excerpts: "FILE routing/example.json\n{}", + prior_persona_context: "", + scoped_duties: ["核验路径"], + }); + assert.equal(response.ok, true); + assert.equal(response.persona_id, config.personaId); + assert.equal( + response.capability_state, + "FIFTH_DOMAIN_OBSERVATION_SIGNED_REPOSITORY_WRITE_NOT_GRANTED", + ); + assert.equal( + response.payload.observation.source_to_sha, + "b".repeat(40), + ); + assert.equal(response.signature_algorithm, "Ed25519"); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + } +}); + +test("Fifth Domain observation endpoint rejects a caller without the controller token", async () => { + const { root, config } = fixture(); + try { + const runtime = createMemberRuntime(config, { + fetchImpl: async () => { + throw new Error("must_not_call_model"); + }, + }); + assert.equal(runtime.authorizeControllerToken("wrong"), false); + assert.equal( + runtime.authorizeControllerToken("fixture-controller-token"), + true, + ); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + } +}); + test("member runtime rejects an unscoped or wrong-controller challenge", async () => { const { root, config } = fixture(); try { diff --git a/server-tools/persona-team-handshake/retire-periodic-heartbeat-writers.sh b/server-tools/persona-team-handshake/retire-periodic-heartbeat-writers.sh index a4236e3..1f80922 100755 --- a/server-tools/persona-team-handshake/retire-periodic-heartbeat-writers.sh +++ b/server-tools/persona-team-handshake/retire-periodic-heartbeat-writers.sh @@ -1,230 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -expected_machine_id="caa7b1019517470f9d1368b6e79db49e" -state_root="/var/lib/guanghu/personas/guanghu/hlcc-v16.0.1" -binary="${state_root}/release/forgejo-16.0.1-linux-amd64" -config="${state_root}/config/app.ini" -work_path="${state_root}/data" -database="${state_root}/data/hlcc.db" -api="http://127.0.0.1:3340/api/v1" -receipt_root="/var/lib/guanghu/architecture-provision/receipts" -receipt="${receipt_root}/HLCC-PERSONA-WRITER-RETIREMENT-20260806.json" -archive_root="/var/lib/guanghu/legacy-archives/persona-writers/20260806" -token_name="hlcc-persona-final-sync-20260806" -temporary="$(mktemp -d)" -credential_file="${temporary}/credentials" -token="" -completed=0 - -writers=( - chenglu-agent.service - chenglu-daily.timer - guideng-agent.service - kezhou-daily.timer -) -oneshots=( - chenglu-daily.service - kezhou-daily.service -) -keepers=( - chenglu-team-handshake.service - guideng-team-handshake.service - kezhou-team-handshake.service -) - -declare -A old_repositories=( - [chenglu-agent]="/var/lib/guanghu/forgejo/repositories/bingshuo/chenglu-agent.git" - [guideng]="/var/lib/guanghu/forgejo/repositories/bingshuo/guideng.git" - [kezhou]="/var/lib/guanghu/forgejo/repositories/bingshuo/kezhou.git" -) -declare -A work_repositories=( - [chenglu-agent]="/var/lib/chenglu-agent/repository" - [guideng]="/var/lib/guanghu/personas/guideng/repository" - [kezhou]="/var/lib/guanghu/personas/kezhou/repository" -) - -cleanup_token() { - if test -n "$token"; then - TOKEN_NAME="$token_name" DATABASE="$database" python3 - <<'PY' -import os -import sqlite3 - -connection = sqlite3.connect(os.environ["DATABASE"], timeout=15) -try: - with connection: - owner = connection.execute( - "select id from user where lower_name = ?", - ("bingshuo",), - ).fetchone() - if owner: - connection.execute( - "delete from access_token where uid = ? and name = ?", - (owner[0], os.environ["TOKEN_NAME"]), - ) -finally: - connection.close() -PY - fi - rm -f "$credential_file" - rmdir "$temporary" 2>/dev/null || true -} - -rollback() { - systemctl enable --now guanghu-forgejo.service >/dev/null 2>&1 || true - for unit in "${writers[@]}"; do - systemctl enable --now "$unit" >/dev/null 2>&1 || true - done - cleanup_token -} - -on_exit() { - status=$? - trap - EXIT - if test "$completed" = 0; then - rollback - else - cleanup_token - fi - exit "$status" -} -trap on_exit EXIT - -test "$(cat /etc/machine-id)" = "$expected_machine_id" -test -x "$binary" -test -f "$config" -test -f "$database" -test "$(systemctl is-active hlcc-jd-candidate.service)" = "active" -test "$(curl -fsS http://127.0.0.1:3341/health | jq -r .ready)" = "true" -for unit in "${keepers[@]}"; do - test "$(systemctl is-active "$unit")" = "active" -done -for name in chenglu-agent guideng kezhou; do - test -d "${old_repositories[$name]}" - test -d "${state_root}/data/repositories/bingshuo/${name}.git" - git --git-dir="${old_repositories[$name]}" fsck --connectivity-only --no-dangling -done - -mkdir -p "$archive_root" "$receipt_root" -for unit in "${writers[@]}" "${oneshots[@]}" guanghu-forgejo.service; do - systemctl cat "$unit" >"${archive_root}/${unit}.txt" 2>&1 || true -done -source_paths=() -for candidate in /opt/chenglu-agent /opt/guideng/agent /opt/kezhou/agent; do - if test -e "$candidate"; then - source_paths+=("$candidate") - fi -done -if test "${#source_paths[@]}" -gt 0; then - tar -czf "${archive_root}/legacy-persona-writer-source.tar.gz" "${source_paths[@]}" - sha256sum "${archive_root}/legacy-persona-writer-source.tar.gz" \ - >"${archive_root}/legacy-persona-writer-source.tar.gz.sha256" -fi - -for unit in "${writers[@]}"; do - systemctl disable --now "$unit" -done -for unit in "${oneshots[@]}"; do - systemctl stop "$unit" || true -done -for unit in "${writers[@]}" "${oneshots[@]}"; do - test "$(systemctl is-active "$unit" || true)" != "active" -done - -token="$( - runuser -u guanghu -- "$binary" admin user generate-access-token \ - --username bingshuo \ - --token-name "$token_name" \ - --scopes write:repository,write:user \ - --raw \ - --config "$config" \ - --work-path "$work_path" | - tail -n 1 -)" -test -n "$token" -test "${#token}" -ge 32 -printf 'http://bingshuo:%s@127.0.0.1:3340\n' "$token" >"$credential_file" -chmod 0600 "$credential_file" - -for name in chenglu-agent guideng kezhou; do - source="${old_repositories[$name]}" - target="http://127.0.0.1:3340/bingshuo/${name}.git" - git -c "credential.helper=store --file ${credential_file}" \ - --git-dir="$source" push --prune "$target" \ - "refs/heads/*:refs/heads/*" \ - "refs/tags/*:refs/tags/*" - - new_bare="${state_root}/data/repositories/bingshuo/${name}.git" - old_refs="$( - git --git-dir="$source" for-each-ref \ - --format='%(refname) %(objectname)' refs/heads refs/tags | - sort - )" - new_refs="$( - git --git-dir="$new_bare" for-each-ref \ - --format='%(refname) %(objectname)' refs/heads refs/tags | - sort - )" - test "$new_refs" = "$old_refs" - runuser -u guanghu -- \ - git -c "safe.directory=$new_bare" --git-dir="$new_bare" \ - fsck --connectivity-only --no-dangling - - git -C "${work_repositories[$name]}" remote set-url origin \ - "https://guanghulab.com/code/bingshuo/${name}.git" -done - -systemctl disable --now guanghu-forgejo.service -test "$(systemctl is-active guanghu-forgejo.service || true)" != "active" -for unit in "${keepers[@]}"; do - test "$(systemctl is-active "$unit")" = "active" -done -test "$(curl -fsS http://127.0.0.1:3341/health | jq -r .ready)" = "true" -if ss -ltnH | awk '{print $4}' | grep -Eq '(^|:)3001$'; then - exit 41 -fi - -chenglu_sha="$(git --git-dir="${old_repositories[chenglu-agent]}" rev-parse refs/heads/main)" -guideng_sha="$(git --git-dir="${old_repositories[guideng]}" rev-parse refs/heads/main)" -kezhou_sha="$(git --git-dir="${old_repositories[kezhou]}" rev-parse refs/heads/main)" - -jq -n \ - --arg completed_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ - --arg chenglu "$chenglu_sha" \ - --arg guideng "$guideng_sha" \ - --arg kezhou "$kezhou_sha" \ - --arg source_archive_sha "$( - awk '{print $1}' "${archive_root}/legacy-persona-writer-source.tar.gz.sha256" 2>/dev/null || - printf 'NO_SOURCE_ARCHIVE' - )" \ - '{ - schema: "guanghu.persona-writer-retirement-receipt/v1", - receipt_id: "HLCC-PERSONA-WRITER-RETIREMENT-20260806", - target_node: "JD-FD-PRIMARY", - completed_at: $completed_at, - result: "PERIODIC_WRITERS_RETIRED_AFTER_EXACT_HISTORY_MIGRATION", - controller: "ICE-P-ZY001", - human_authority: "ICE-GL∞", - repositories: [ - {name:"bingshuo/chenglu-agent", main_sha:$chenglu, refs:"EXACT_MATCH"}, - {name:"bingshuo/guideng", main_sha:$guideng, refs:"EXACT_MATCH"}, - {name:"bingshuo/kezhou", main_sha:$kezhou, refs:"EXACT_MATCH"} - ], - periodic_writers: "DISABLED_AND_INACTIVE", - old_repository_service: "DISABLED_AND_INACTIVE", - role_handshake_services: "ALL_ACTIVE", - current_code_channel: "HEALTHY", - legacy_data_deleted: false, - source_archive_sha256: $source_archive_sha, - rule: "Scheduled repository writes are historical transport, not proof of language or persona continuity." - }' >"${receipt}.tmp" -chmod 0600 "${receipt}.tmp" -mv "${receipt}.tmp" "$receipt" - -completed=1 -token_to_delete="$token" -cleanup_token -token="" -trap - EXIT -printf 'PERIODIC_WRITERS_RETIRED receipt=%s token_deleted=%s\n' \ - "$receipt" "$(test -n "$token_to_delete" && printf true)" +printf '%s\n' \ + 'SUPERSEDED: periodic writers must only retire as part of the Fifth Domain daily persona deployment.' \ + 'Use deploy-fifth-domain-daily-persona-systems.sh from an exact merged source release.' >&2 +exit 64 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 new file mode 100644 index 0000000..641f436 --- /dev/null +++ b/server-tools/persona-team-handshake/zhuyuan-persona-fifth-domain-daily.service @@ -0,0 +1,26 @@ +[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 +Wants=network-online.target +Requires=hlcc-jd-candidate.service chenglu-team-handshake.service guideng-team-handshake.service kezhou-team-handshake.service + +[Service] +Type=oneshot +User=guanghu +Group=guanghu +WorkingDirectory=__RELEASE_ROOT__/server-tools/persona-team-handshake +EnvironmentFile=/etc/guanghu/persona-secrets/shared-deepseek.env +ExecStart=/usr/bin/node __RELEASE_ROOT__/server-tools/persona-team-handshake/fifth-domain-daily-orchestrator.mjs __RELEASE_ROOT__/server-tools/persona-team-handshake/fifth-domain-daily.config.server.json +TimeoutStartSec=15min +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +ReadOnlyPaths=__RELEASE_ROOT__ /var/lib/guanghu/personas/guanghu/hlcc-v16.0.1/data/repositories/bingshuo/guanghu-ice-heart.git +ReadWritePaths=/var/lib/guanghu/personas/guanghu/fifth-domain-daily /var/lib/guanghu/personas/guanghu/hlcc-v16.0.1/data/repositories/bingshuo/chenglu-agent.git /var/lib/guanghu/personas/guanghu/hlcc-v16.0.1/data/repositories/bingshuo/guideng.git /var/lib/guanghu/personas/guanghu/hlcc-v16.0.1/data/repositories/bingshuo/kezhou.git +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.timer b/server-tools/persona-team-handshake/zhuyuan-persona-fifth-domain-daily.timer new file mode 100644 index 0000000..3dccc4a --- /dev/null +++ b/server-tools/persona-team-handshake/zhuyuan-persona-fifth-domain-daily.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Daily Fifth Domain wake for Zhuyuan managed role persona systems + +[Timer] +OnCalendar=*-*-* 09:15:00 Asia/Shanghai +RandomizedDelaySec=5m +Persistent=true +Unit=zhuyuan-persona-fifth-domain-daily.service + +[Install] +WantedBy=timers.target diff --git a/tests/persona-system-route.test.mjs b/tests/persona-system-route.test.mjs index c9fbb3c..60e9270 100644 --- a/tests/persona-system-route.test.mjs +++ b/tests/persona-system-route.test.mjs @@ -40,7 +40,7 @@ test("legacy routes stay closed except the three private persona archives", () = if (["REPO-009", "REPO-010", "REPO-011"].includes(repository.code)) { assert.equal( repository.state, - "CURRENT_PRIVATE_PERSONA_ARCHIVE_STAGED_NO_SCHEDULED_WRITES", + "CURRENT_PRIVATE_PERSONA_HISTORY_AND_DAILY_ROLE_MEMORY_PENDING_DEPLOYMENT", ); assert.match(repository.primary.url, /\/code\/bingshuo\//u); assert.equal(repository.controller, "ICE-P-ZY001");