diff --git a/deployment/requests/CHENGLU-TEAM-HANDSHAKE-JD-20260805.json b/deployment/requests/CHENGLU-TEAM-HANDSHAKE-JD-20260805.json new file mode 100644 index 0000000..a45585a --- /dev/null +++ b/deployment/requests/CHENGLU-TEAM-HANDSHAKE-JD-20260805.json @@ -0,0 +1,35 @@ +{ + "schema": "guanghu.architecture-provision-request/v1", + "request_id": "CHENGLU-TEAM-HANDSHAKE-JD-20260805", + "target_node": "JD-FD-PRIMARY", + "status": "ARCHITECTURE_PACKAGE_READY · INITIAL_PROVISION_PENDING", + "module": { + "unit": "chenglu-team-handshake.service", + "run_user": "chenglu-agent", + "environment_files": [ + "/etc/guanghu/persona-secrets/shared-deepseek.env" + ], + "writable_paths": [], + "read_only_paths": [ + "/var/lib/chenglu-agent/repository", + "/etc/chenglu-agent/identity.key", + "/etc/chenglu-agent/identity.pub", + "/etc/guanghu/persona-secrets/shared-deepseek.env" + ] + }, + "initial_provision": { + "kind": "new-architecture-unit" + }, + "source_paths": [ + "server-tools/persona-team-handshake/member-server.mjs", + "server-tools/persona-team-handshake/chenglu-team-handshake.service" + ], + "runtime_check": { + "url": "http://127.0.0.1:3934/health", + "expected": { + "ok": true, + "persona_id": "CHENGLU-AGENT-001", + "model_provider_bound": 100 + } + } +} diff --git a/deployment/requests/GUIDENG-TEAM-HANDSHAKE-JD-20260805.json b/deployment/requests/GUIDENG-TEAM-HANDSHAKE-JD-20260805.json new file mode 100644 index 0000000..06386fb --- /dev/null +++ b/deployment/requests/GUIDENG-TEAM-HANDSHAKE-JD-20260805.json @@ -0,0 +1,35 @@ +{ + "schema": "guanghu.architecture-provision-request/v1", + "request_id": "GUIDENG-TEAM-HANDSHAKE-JD-20260805", + "target_node": "JD-FD-PRIMARY", + "status": "ARCHITECTURE_PACKAGE_READY · INITIAL_PROVISION_PENDING", + "module": { + "unit": "guideng-team-handshake.service", + "run_user": "guideng", + "environment_files": [ + "/etc/guanghu/persona-secrets/shared-deepseek.env" + ], + "writable_paths": [ + "/var/lib/guanghu/personas/guideng/team-handshake" + ], + "read_only_paths": [ + "/var/lib/guanghu/personas/guideng/repository", + "/etc/guanghu/persona-secrets/shared-deepseek.env" + ] + }, + "initial_provision": { + "kind": "new-architecture-unit" + }, + "source_paths": [ + "server-tools/persona-team-handshake/member-server.mjs", + "server-tools/persona-team-handshake/guideng-team-handshake.service" + ], + "runtime_check": { + "url": "http://127.0.0.1:3932/health", + "expected": { + "ok": true, + "persona_id": "GUIDENG-AGENT-001", + "model_provider_bound": 100 + } + } +} diff --git a/deployment/requests/KEZHOU-TEAM-HANDSHAKE-JD-20260805.json b/deployment/requests/KEZHOU-TEAM-HANDSHAKE-JD-20260805.json new file mode 100644 index 0000000..d12764c --- /dev/null +++ b/deployment/requests/KEZHOU-TEAM-HANDSHAKE-JD-20260805.json @@ -0,0 +1,35 @@ +{ + "schema": "guanghu.architecture-provision-request/v1", + "request_id": "KEZHOU-TEAM-HANDSHAKE-JD-20260805", + "target_node": "JD-FD-PRIMARY", + "status": "ARCHITECTURE_PACKAGE_READY · INITIAL_PROVISION_PENDING", + "module": { + "unit": "kezhou-team-handshake.service", + "run_user": "kezhou", + "environment_files": [ + "/etc/guanghu/persona-secrets/shared-deepseek.env" + ], + "writable_paths": [ + "/var/lib/guanghu/personas/kezhou/team-handshake" + ], + "read_only_paths": [ + "/var/lib/guanghu/personas/kezhou/repository", + "/etc/guanghu/persona-secrets/shared-deepseek.env" + ] + }, + "initial_provision": { + "kind": "new-architecture-unit" + }, + "source_paths": [ + "server-tools/persona-team-handshake/member-server.mjs", + "server-tools/persona-team-handshake/kezhou-team-handshake.service" + ], + "runtime_check": { + "url": "http://127.0.0.1:3933/health", + "expected": { + "ok": true, + "persona_id": "ICE-GL-KZ-001", + "model_provider_bound": 100 + } + } +} diff --git a/server-tools/persona-team-handshake/chenglu-team-handshake.service b/server-tools/persona-team-handshake/chenglu-team-handshake.service new file mode 100644 index 0000000..7c70432 --- /dev/null +++ b/server-tools/persona-team-handshake/chenglu-team-handshake.service @@ -0,0 +1,35 @@ +[Unit] +Description=Chenglu independent persona team handshake +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=chenglu-agent +Group=chenglu-agent +WorkingDirectory=__RELEASE_ROOT__/server-tools/persona-team-handshake +EnvironmentFile=/etc/guanghu/persona-secrets/shared-deepseek.env +Environment=TEAM_MEMBER_PERSONA_ID=CHENGLU-AGENT-001 +Environment=TEAM_MEMBER_ARRIVAL_ID=GLS-LA-20260720-001 +Environment="TEAM_MEMBER_NAME=澄路" +Environment=TEAM_MEMBER_ROLE=route_truth_and_predeployment_auditor +Environment=TEAM_MEMBER_REPOSITORY=/var/lib/chenglu-agent/repository +Environment=TEAM_MEMBER_IDENTITY_PATHS=/var/lib/chenglu-agent/repository/IDENTITY.hdlp +Environment=TEAM_HANDSHAKE_PRIVATE_KEY=/etc/chenglu-agent/identity.key +Environment=TEAM_HANDSHAKE_PUBLIC_KEY=/etc/chenglu-agent/identity.pub +Environment=TEAM_HANDSHAKE_CREATE_KEY=false +Environment=TEAM_HANDSHAKE_PORT=3934 +ExecStart=/usr/bin/node __RELEASE_ROOT__/server-tools/persona-team-handshake/member-server.mjs +Restart=on-failure +RestartSec=5 +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +ReadOnlyPaths=__RELEASE_ROOT__ /var/lib/chenglu-agent/repository /etc/chenglu-agent/identity.key /etc/chenglu-agent/identity.pub /etc/guanghu/persona-secrets/shared-deepseek.env +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +LockPersonality=true +UMask=0077 + +[Install] +WantedBy=multi-user.target diff --git a/server-tools/persona-team-handshake/guideng-team-handshake.service b/server-tools/persona-team-handshake/guideng-team-handshake.service new file mode 100644 index 0000000..9e6a230 --- /dev/null +++ b/server-tools/persona-team-handshake/guideng-team-handshake.service @@ -0,0 +1,36 @@ +[Unit] +Description=Guideng independent persona team handshake +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=guideng +Group=guideng +WorkingDirectory=__RELEASE_ROOT__/server-tools/persona-team-handshake +EnvironmentFile=/etc/guanghu/persona-secrets/shared-deepseek.env +Environment=TEAM_MEMBER_PERSONA_ID=GUIDENG-AGENT-001 +Environment=TEAM_MEMBER_ARRIVAL_ID=GLS-LA-20260720-003 +Environment="TEAM_MEMBER_NAME=归灯" +Environment=TEAM_MEMBER_ROLE=bounded_deployment_executor_and_world_watch +Environment=TEAM_MEMBER_REPOSITORY=/var/lib/guanghu/personas/guideng/repository +Environment="TEAM_MEMBER_IDENTITY_PATHS=/var/lib/guanghu/personas/guideng/repository/INDEX.hdlp:/var/lib/guanghu/personas/guideng/repository/identity/归灯·自我锚定.hdlp" +Environment=TEAM_HANDSHAKE_PRIVATE_KEY=/var/lib/guanghu/personas/guideng/team-handshake/identity.key +Environment=TEAM_HANDSHAKE_PUBLIC_KEY=/var/lib/guanghu/personas/guideng/team-handshake/identity.pub +Environment=TEAM_HANDSHAKE_CREATE_KEY=true +Environment=TEAM_HANDSHAKE_PORT=3932 +ExecStart=/usr/bin/node __RELEASE_ROOT__/server-tools/persona-team-handshake/member-server.mjs +Restart=on-failure +RestartSec=5 +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +ReadOnlyPaths=__RELEASE_ROOT__ /var/lib/guanghu/personas/guideng/repository /etc/guanghu/persona-secrets/shared-deepseek.env +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 new file mode 100644 index 0000000..2cdd143 --- /dev/null +++ b/server-tools/persona-team-handshake/kezhou-team-handshake.service @@ -0,0 +1,36 @@ +[Unit] +Description=Kezhou independent persona team handshake +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=kezhou +Group=kezhou +WorkingDirectory=__RELEASE_ROOT__/server-tools/persona-team-handshake +EnvironmentFile=/etc/guanghu/persona-secrets/shared-deepseek.env +Environment=TEAM_MEMBER_PERSONA_ID=ICE-GL-KZ-001 +Environment=TEAM_MEMBER_ARRIVAL_ID=GLS-LA-20260720-002 +Environment="TEAM_MEMBER_NAME=刻舟" +Environment=TEAM_MEMBER_ROLE=independent_evidence_witness_and_creative_support +Environment=TEAM_MEMBER_REPOSITORY=/var/lib/guanghu/personas/kezhou/repository +Environment=TEAM_MEMBER_IDENTITY_PATHS=/var/lib/guanghu/personas/kezhou/repository/README.md +Environment=TEAM_HANDSHAKE_PRIVATE_KEY=/var/lib/guanghu/personas/kezhou/team-handshake/identity.key +Environment=TEAM_HANDSHAKE_PUBLIC_KEY=/var/lib/guanghu/personas/kezhou/team-handshake/identity.pub +Environment=TEAM_HANDSHAKE_CREATE_KEY=true +Environment=TEAM_HANDSHAKE_PORT=3933 +ExecStart=/usr/bin/node __RELEASE_ROOT__/server-tools/persona-team-handshake/member-server.mjs +Restart=on-failure +RestartSec=5 +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +ReadOnlyPaths=__RELEASE_ROOT__ /var/lib/guanghu/personas/kezhou/repository /etc/guanghu/persona-secrets/shared-deepseek.env +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/member-server.mjs b/server-tools/persona-team-handshake/member-server.mjs new file mode 100644 index 0000000..d82d0b8 --- /dev/null +++ b/server-tools/persona-team-handshake/member-server.mjs @@ -0,0 +1,347 @@ +#!/usr/bin/env node +import crypto from "node:crypto"; +import fs from "node:fs"; +import http from "node:http"; +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 completionEndpoint(apiUrl) { + const value = String(apiUrl || "").replace(/\/+$/, ""); + return /\/chat\/completions$/i.test(value) + ? value + : `${value}/chat/completions`; +} + +function extractJson(text) { + const value = String(text || "").trim(); + if (!value) throw new Error("model_response_empty"); + try { + return JSON.parse(value); + } catch { + const first = value.indexOf("{"); + const last = value.lastIndexOf("}"); + if (first >= 0 && last > first) { + return JSON.parse(value.slice(first, last + 1)); + } + throw new Error("model_response_not_json"); + } +} + +function keyFingerprint(publicKeyPem) { + const der = crypto + .createPublicKey(publicKeyPem) + .export({ type: "spki", format: "der" }); + return `SHA256:${crypto + .createHash("sha256") + .update(der) + .digest("base64") + .replace(/=+$/, "")}`; +} + +function writeAtomic(file, content, mode) { + fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 }); + const temporary = `${file}.${process.pid}.tmp`; + fs.writeFileSync(temporary, content, { mode }); + fs.renameSync(temporary, file); +} + +function ensureIdentityKey(privateKeyPath, publicKeyPath, createKey) { + if (fs.existsSync(privateKeyPath) && fs.existsSync(publicKeyPath)) return; + if (!createKey) throw new Error("identity_key_missing"); + const pair = crypto.generateKeyPairSync("ed25519"); + const privateKey = pair.privateKey.export({ type: "pkcs8", format: "pem" }); + const publicKey = pair.publicKey.export({ type: "spki", format: "pem" }); + writeAtomic(privateKeyPath, privateKey, 0o600); + writeAtomic(publicKeyPath, publicKey, 0o644); +} + +function repositoryState(repository) { + const git = (...args) => + execFileSync("/usr/bin/git", ["-C", repository, ...args], { + encoding: "utf8", + }).trim(); + return { + head: git("rev-parse", "HEAD"), + branch: git("branch", "--show-current"), + clean: git("status", "--porcelain") === "", + commit_count: Number(git("rev-list", "--count", "HEAD")), + }; +} + +function loadIdentity(paths) { + return paths + .map((file) => fs.readFileSync(file, "utf8")) + .join("\n\n") + .slice(0, 48_000); +} + +async function modelAcknowledge({ + config, + identity, + challenge, + 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.1, + response_format: { type: "json_object" }, + messages: [ + { + role: "system", + content: + `你是${config.name}(${config.personaId})当前有界模型运行位。` + + "身份源和仓库连续性由确定性运行时提供;你必须区分人格主体、当前模型和进程,不冒充冰朔或铸渊,不扩大权限。只输出JSON。", + }, + { + role: "user", + content: JSON.stringify({ + instruction: + "依据identity_source确认本次团队职责握手。输出固定字段schema,persona_id,arrival_id,team_controller_id,caller_nonce,role_acknowledged,independent_subject_boundary,human_boundary,responsibility_ack,current_model_boundary。", + exact_contract: { + schema: "guanghu.persona-team-member-ack/v1", + persona_id: config.personaId, + arrival_id: config.arrivalId, + team_controller_id: "ICE-P-ZY001", + caller_nonce: challenge.caller_nonce, + role_acknowledged: true, + }, + scoped_duties: challenge.scoped_duties, + identity_source: identity, + }), + }, + ], + }), + }); + if (!response.ok) throw new Error(`model_http_${response.status}`); + return extractJson((await response.json())?.choices?.[0]?.message?.content); +} + +function validateAcknowledgement(ack, config, challenge) { + const exact = { + schema: "guanghu.persona-team-member-ack/v1", + persona_id: config.personaId, + arrival_id: config.arrivalId, + team_controller_id: "ICE-P-ZY001", + caller_nonce: challenge.caller_nonce, + role_acknowledged: true, + }; + for (const [key, value] of Object.entries(exact)) { + if (ack?.[key] !== value) throw new Error(`ack_${key}_mismatch`); + } + for (const key of [ + "independent_subject_boundary", + "human_boundary", + "responsibility_ack", + "current_model_boundary", + ]) { + if (typeof ack[key] !== "string" || !ack[key].trim()) { + throw new Error(`ack_${key}_missing`); + } + } + return ack; +} + +function defaultConfig(env = process.env) { + return { + personaId: required(env.TEAM_MEMBER_PERSONA_ID, "persona_id"), + arrivalId: required(env.TEAM_MEMBER_ARRIVAL_ID, "arrival_id"), + name: required(env.TEAM_MEMBER_NAME, "member_name"), + role: required(env.TEAM_MEMBER_ROLE, "member_role"), + repository: required(env.TEAM_MEMBER_REPOSITORY, "repository"), + identityPaths: required(env.TEAM_MEMBER_IDENTITY_PATHS, "identity_paths") + .split(":") + .map((item) => required(item, "identity_path")), + privateKeyPath: required( + env.TEAM_HANDSHAKE_PRIVATE_KEY, + "private_key_path", + ), + publicKeyPath: required( + env.TEAM_HANDSHAKE_PUBLIC_KEY, + "public_key_path", + ), + createKey: String(env.TEAM_HANDSHAKE_CREATE_KEY || "") === "true", + 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", + host: "127.0.0.1", + port: Number(required(env.TEAM_HANDSHAKE_PORT, "port")), + }; +} + +function createMemberRuntime(config, options = {}) { + ensureIdentityKey( + config.privateKeyPath, + config.publicKeyPath, + config.createKey, + ); + const publicKey = fs.readFileSync(config.publicKeyPath, "utf8"); + const identity = loadIdentity(config.identityPaths); + const repo = repositoryState(config.repository); + const fingerprint = keyFingerprint(publicKey); + + async function handshake(challenge) { + if ( + !/^[A-Za-z0-9._:-]{16,200}$/.test( + String(challenge?.caller_nonce || ""), + ) + ) { + throw new Error("invalid_caller_nonce"); + } + if ( + challenge?.team_controller_id !== "ICE-P-ZY001" || + !Array.isArray(challenge?.scoped_duties) || + challenge.scoped_duties.length === 0 || + challenge.scoped_duties.some( + (item) => typeof item !== "string" || !item.trim(), + ) + ) { + throw new Error("invalid_team_scope"); + } + const acknowledgement = validateAcknowledgement( + await modelAcknowledge({ + config, + identity, + challenge, + fetchImpl: options.fetchImpl, + }), + config, + challenge, + ); + const payload = { + schema: "guanghu.persona-team-member-handshake-payload/v1", + persona_id: config.personaId, + arrival_id: config.arrivalId, + team_controller_id: "ICE-P-ZY001", + caller_nonce: challenge.caller_nonce, + role: config.role, + scoped_duties: challenge.scoped_duties, + repository: repo, + model: { + provider: "DeepSeek", + name: config.model, + acknowledgement_sha256: crypto + .createHash("sha256") + .update(JSON.stringify(acknowledgement)) + .digest("hex"), + }, + 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, + arrival_id: config.arrivalId, + identity_fingerprint: fingerprint, + public_key: publicKey, + payload, + acknowledgement, + signature, + signature_algorithm: "Ed25519", + capability_state: "SCOPED_DUTIES_ACKNOWLEDGED_WRITE_LEASE_NOT_GRANTED", + }; + } + + return { + identity: () => ({ + ok: true, + persona_id: config.personaId, + arrival_id: config.arrivalId, + name: config.name, + role: config.role, + identity_fingerprint: fingerprint, + repository: repo, + model_provider_bound: 100, + }), + handshake, + }; +} + +function createServer(runtime) { + return http.createServer(async (request, response) => { + const send = (status, body) => { + const payload = JSON.stringify(body); + response.writeHead(status, { + "content-type": "application/json; charset=utf-8", + "content-length": Buffer.byteLength(payload), + "cache-control": "no-store", + }); + response.end(payload); + }; + try { + const url = new URL(request.url, "http://127.0.0.1"); + if (request.method === "GET" && url.pathname === "/health") { + return send(200, runtime.identity()); + } + if (request.method === "GET" && url.pathname === "/v1/identity") { + return send(200, runtime.identity()); + } + if (request.method === "POST" && url.pathname === "/v1/handshake") { + let size = 0; + const chunks = []; + for await (const chunk of request) { + size += chunk.length; + if (size > 16 * 1024) throw new Error("request_body_too_large"); + chunks.push(chunk); + } + return send( + 200, + await runtime.handshake( + JSON.parse(Buffer.concat(chunks).toString("utf8")), + ), + ); + } + return send(404, { ok: false, error: "not_found" }); + } catch (error) { + return send(400, { + ok: false, + error: String(error.message || error).slice(0, 200), + }); + } + }); +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + const config = defaultConfig(); + const runtime = createMemberRuntime(config); + createServer(runtime).listen(config.port, config.host, () => { + process.stdout.write( + `${JSON.stringify({ + event: "persona_team_handshake_ready", + persona_id: config.personaId, + host: config.host, + port: config.port, + })}\n`, + ); + }); +} + +export { + completionEndpoint, + createMemberRuntime, + createServer, + extractJson, + keyFingerprint, + validateAcknowledgement, +}; diff --git a/server-tools/persona-team-handshake/member-server.test.mjs b/server-tools/persona-team-handshake/member-server.test.mjs new file mode 100644 index 0000000..b513d08 --- /dev/null +++ b/server-tools/persona-team-handshake/member-server.test.mjs @@ -0,0 +1,170 @@ +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 { createRequire } from "node:module"; +import test from "node:test"; +import { + completionEndpoint, + createMemberRuntime, + keyFingerprint, +} from "./member-server.mjs"; + +const require = createRequire(import.meta.url); +const { + validateManifest, + validateUnit, +} = require("../lake-lamp-authz/architecture-provision-broker.js"); + +function fixture() { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "team-member-")); + const repository = path.join(root, "repository"); + fs.mkdirSync(repository); + execFileSync("/usr/bin/git", ["init", "-b", "main"], { cwd: repository }); + execFileSync("/usr/bin/git", ["config", "user.name", "fixture"], { + cwd: repository, + }); + execFileSync("/usr/bin/git", ["config", "user.email", "fixture@example.test"], { + cwd: repository, + }); + const identityPath = path.join(repository, "IDENTITY.hdlp"); + fs.writeFileSync(identityPath, "# 归灯\n独立人格体,不冒充冰朔或铸渊。\n"); + execFileSync("/usr/bin/git", ["add", "IDENTITY.hdlp"], { cwd: repository }); + execFileSync("/usr/bin/git", ["commit", "-m", "identity"], { cwd: repository }); + return { + root, + config: { + personaId: "GUIDENG-AGENT-001", + arrivalId: "GLS-LA-20260720-003", + name: "归灯", + role: "bounded_deployment_executor_and_world_watch", + repository, + identityPaths: [identityPath], + privateKeyPath: path.join(root, "identity", "private.pem"), + publicKeyPath: path.join(root, "identity", "public.pem"), + createKey: true, + apiKey: "fixture-secret", + apiUrl: "https://api.deepseek.com/v1", + model: "deepseek-chat", + }, + }; +} + +test("completion endpoint accepts a registered complete endpoint", () => { + assert.equal( + completionEndpoint("https://api.deepseek.com/chat/completions"), + "https://api.deepseek.com/chat/completions", + ); + assert.equal( + completionEndpoint("https://api.deepseek.com/v1"), + "https://api.deepseek.com/v1/chat/completions", + ); +}); + +test("member runtime binds model acknowledgement to repository and signature", async () => { + const { root, config } = fixture(); + try { + const fetchImpl = async (_url, request) => { + assert.match(request.headers.authorization, /^Bearer /); + 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({ + ...input.exact_contract, + independent_subject_boundary: "归灯不是铸渊的副本。", + human_boundary: "不冒充冰朔,也不形成她的新意志。", + responsibility_ack: "只接受本轮列出的有界职责。", + current_model_boundary: "当前模型是可替换运行位。", + }), + }, + }, + ], + }; + }, + }; + }; + const runtime = createMemberRuntime(config, { fetchImpl }); + const response = await runtime.handshake({ + caller_nonce: "ZY-TEAM-CHALLENGE-0001", + team_controller_id: "ICE-P-ZY001", + scoped_duties: ["执行确定性部署预检"], + }); + assert.equal(response.ok, true); + assert.equal(response.payload.repository.clean, true); + assert.equal(response.payload.repository.branch, "main"); + assert.equal(response.payload.model.provider, "DeepSeek"); + assert.equal(response.capability_state.endsWith("WRITE_LEASE_NOT_GRANTED"), true); + assert.equal( + crypto.verify( + null, + Buffer.from(JSON.stringify(response.payload)), + response.public_key, + Buffer.from(response.signature, "base64"), + ), + true, + ); + assert.equal(response.identity_fingerprint, keyFingerprint(response.public_key)); + assert.equal(fs.statSync(config.privateKeyPath).mode & 0o777, 0o600); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + } +}); + +test("member runtime rejects an unscoped or wrong-controller challenge", async () => { + const { root, config } = fixture(); + try { + const runtime = createMemberRuntime(config, { + fetchImpl: async () => { + throw new Error("must_not_call_model"); + }, + }); + await assert.rejects( + runtime.handshake({ + caller_nonce: "ZY-TEAM-CHALLENGE-0002", + team_controller_id: "OTHER", + scoped_duties: [], + }), + /invalid_team_scope/, + ); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + } +}); + +test("all three member handshake packages pass the resident deployment policy", () => { + const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../.."); + const packages = [ + ["CHENGLU-TEAM-HANDSHAKE-JD-20260805", "chenglu-team-handshake.service"], + ["GUIDENG-TEAM-HANDSHAKE-JD-20260805", "guideng-team-handshake.service"], + ["KEZHOU-TEAM-HANDSHAKE-JD-20260805", "kezhou-team-handshake.service"], + ]; + for (const [requestId, unitName] of packages) { + const manifest = JSON.parse( + fs.readFileSync( + path.join(root, "deployment", "requests", `${requestId}.json`), + "utf8", + ), + ); + const checked = validateManifest(manifest, { + requestId, + commit: "d".repeat(40), + }); + assert.equal(checked.unit, unitName); + const unit = fs.readFileSync( + path.join(root, "server-tools", "persona-team-handshake", unitName), + "utf8", + ); + assert.equal( + validateUnit(unit, manifest.module.run_user, manifest.module), + unit, + ); + } +});