部署:接通三套人格系统每日第五域巡游
This commit is contained in:
parent
d5ccd073cf
commit
f7ef839ced
20 changed files with 1413 additions and 275 deletions
425
server-tools/persona-team-handshake/fifth-domain-daily-orchestrator.mjs
Executable file
425
server-tools/persona-team-handshake/fifth-domain-daily-orchestrator.mjs
Executable file
|
|
@ -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,
|
||||
};
|
||||
Loading…
Reference in a new issue