feat: add independent persona team handshakes
This commit is contained in:
parent
b418a9368a
commit
f4977ee9f7
8 changed files with 729 additions and 0 deletions
35
deployment/requests/CHENGLU-TEAM-HANDSHAKE-JD-20260805.json
Normal file
35
deployment/requests/CHENGLU-TEAM-HANDSHAKE-JD-20260805.json
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
deployment/requests/GUIDENG-TEAM-HANDSHAKE-JD-20260805.json
Normal file
35
deployment/requests/GUIDENG-TEAM-HANDSHAKE-JD-20260805.json
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
deployment/requests/KEZHOU-TEAM-HANDSHAKE-JD-20260805.json
Normal file
35
deployment/requests/KEZHOU-TEAM-HANDSHAKE-JD-20260805.json
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
347
server-tools/persona-team-handshake/member-server.mjs
Normal file
347
server-tools/persona-team-handshake/member-server.mjs
Normal file
|
|
@ -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,
|
||||||
|
};
|
||||||
170
server-tools/persona-team-handshake/member-server.test.mjs
Normal file
170
server-tools/persona-team-handshake/member-server.test.mjs
Normal file
|
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue