589 lines
18 KiB
JavaScript
589 lines
18 KiB
JavaScript
#!/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 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)
|
||
? 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,
|
||
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.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,
|
||
},
|
||
validator_error_from_previous_attempt: validatorError,
|
||
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 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",
|
||
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",
|
||
controllerToken: required(
|
||
env.PERSONA_TEAM_CONTROLLER_TOKEN,
|
||
"persona_team_controller_token",
|
||
),
|
||
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");
|
||
}
|
||
let acknowledgement = null;
|
||
let validatorError = null;
|
||
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
||
try {
|
||
acknowledgement = validateAcknowledgement(
|
||
await modelAcknowledge({
|
||
config,
|
||
identity,
|
||
challenge,
|
||
validatorError,
|
||
fetchImpl: options.fetchImpl,
|
||
}),
|
||
config,
|
||
challenge,
|
||
);
|
||
break;
|
||
} catch (error) {
|
||
validatorError = String(error.message || error).slice(0, 200);
|
||
if (attempt === 3) throw error;
|
||
}
|
||
}
|
||
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",
|
||
};
|
||
}
|
||
|
||
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,
|
||
arrival_id: config.arrivalId,
|
||
name: config.name,
|
||
role: config.role,
|
||
identity_fingerprint: fingerprint,
|
||
repository: repo,
|
||
model_provider_bound: 100,
|
||
}),
|
||
handshake,
|
||
observeFifthDomain,
|
||
};
|
||
}
|
||
|
||
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 > 256 * 1024) throw new Error("request_body_too_large");
|
||
chunks.push(chunk);
|
||
}
|
||
return send(
|
||
200,
|
||
await runtime.handshake(
|
||
JSON.parse(Buffer.concat(chunks).toString("utf8")),
|
||
),
|
||
);
|
||
}
|
||
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, {
|
||
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,
|
||
modelObserveFifthDomain,
|
||
timingSafeTextEqual,
|
||
validateAcknowledgement,
|
||
validateFifthDomainEvent,
|
||
validateFifthDomainObservation,
|
||
};
|