部署:接通三套人格系统每日第五域巡游
This commit is contained in:
parent
d5ccd073cf
commit
f7ef839ced
20 changed files with 1413 additions and 275 deletions
292
server-tools/persona-team-handshake/deploy-fifth-domain-daily-persona-systems.sh
Executable file
292
server-tools/persona-team-handshake/deploy-fifth-domain-daily-persona-systems.sh
Executable file
|
|
@ -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"
|
||||
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,
|
||||
};
|
||||
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
|
|
@ -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": [
|
||||
"记录第五域计划与现实证据差异",
|
||||
"发现对创作支持和证据保存有意义的更新"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
Loading…
Reference in a new issue