207 lines
14 KiB
JavaScript
207 lines
14 KiB
JavaScript
"use strict";
|
|
|
|
const test = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const fs = require("node:fs");
|
|
const os = require("node:os");
|
|
const path = require("node:path");
|
|
const { parseResource, safeRelative, validateManifest, validateUnit, validateUpdateUnit, provision } = require("./architecture-provision-broker");
|
|
|
|
const commit = "d".repeat(40);
|
|
const requestId = "GLS-0231-JD-LAN-01-INITIAL-PROVISION-20260720";
|
|
|
|
function manifest() {
|
|
return {
|
|
schema: "guanghu.architecture-provision-request/v1", request_id: requestId, target_node: "JD-FD-PRIMARY", status: "ARCHITECTURE_PACKAGE_READY · INITIAL_PROVISION_PENDING",
|
|
module: { unit: "example.service", run_user: "guanghu" }, initial_provision: { kind: "new-architecture-unit" },
|
|
source_paths: ["server-tools/example/server.js", "server-tools/example/example.service"],
|
|
runtime_check: { url: "http://127.0.0.1:3924/health", expected: { ok: true, mode: "read-only" } },
|
|
};
|
|
}
|
|
|
|
test("resource and manifest are immutable and path constrained", () => {
|
|
assert.deepEqual(parseResource(`${requestId}@${commit}`), { requestId, commit });
|
|
assert.equal(parseResource(`${requestId}@main`), null);
|
|
assert.equal(safeRelative("server-tools/example/server.js"), true);
|
|
assert.equal(safeRelative("../etc/passwd"), false);
|
|
assert.equal(validateManifest(manifest(), { requestId, commit }).unit, "example.service");
|
|
});
|
|
|
|
test("architecture deployment defaults to the current Fifth Domain code channel", () => {
|
|
const source = fs.readFileSync(path.join(__dirname, "architecture-provision-broker.js"), "utf8");
|
|
const environment = fs.readFileSync(path.join(__dirname, "authorization.env.example"), "utf8");
|
|
assert.match(source, /https:\/\/guanghulab\.com\/code\/bingshuo\/guanghu-ice-heart\.git/);
|
|
assert.match(environment, /^ARCHITECTURE_PROVISION_REPO_URL=https:\/\/guanghulab\.com\/code\/bingshuo\/guanghu-ice-heart\.git$/m);
|
|
assert.doesNotMatch(source, /ARCHITECTURE_PROVISION_REPO_URL \|\| "https:\/\/guanghulab\.com\/fifth-domain\//);
|
|
});
|
|
|
|
test("unit requires non-root systemd hardening and release placeholder", () => {
|
|
const unit = "[Service]\nUser=guanghu\nGroup=guanghu\nNoNewPrivileges=true\nPrivateTmp=true\nProtectSystem=strict\nProtectHome=true\nExecStart=/usr/bin/node __RELEASE_ROOT__/server.js\n";
|
|
assert.equal(validateUnit(unit), unit);
|
|
assert.throws(() => validateUnit(unit.replace("User=guanghu", "User=root")), /dedicated_service_user_required/);
|
|
assert.throws(() => validateUnit(`${unit}EnvironmentFile=/etc/shadow\n`), /environment_file_not_declared/);
|
|
assert.throws(() => validateUnit(unit.replace("__RELEASE_ROOT__", "/tmp/live")), /release_root_placeholder_required/);
|
|
});
|
|
|
|
test("unit permits a declared persona user, shared secret and state directory", () => {
|
|
const unit = "[Service]\nUser=kezhou\nGroup=kezhou\nNoNewPrivileges=yes\nPrivateTmp=yes\nProtectSystem=strict\nProtectHome=yes\nEnvironmentFile=-/etc/guanghu/persona-secrets/shared-deepseek.env\nReadWritePaths=/var/lib/guanghu/personas/kezhou\nReadOnlyPaths=__RELEASE_ROOT__\nExecStart=__RELEASE_ROOT__/run.sh\n";
|
|
const policy = { environment_files: ["/etc/guanghu/persona-secrets/shared-deepseek.env"], writable_paths: ["/var/lib/guanghu/personas/kezhou"], read_only_paths: [] };
|
|
assert.equal(validateUnit(unit, "kezhou", policy), unit);
|
|
assert.throws(() => validateUnit(unit.replace("shared-deepseek.env", "../../shadow"), "kezhou", policy), /environment_file_not_declared/);
|
|
assert.throws(() => validateUnit(unit.replace("/var/lib/guanghu/personas/kezhou", "/opt/guanghu/personas/kezhou"), "kezhou", policy), /writable_path_not_declared/);
|
|
assert.throws(() => validateUnit(unit.replaceAll("kezhou", "root"), "root", policy), /dedicated_service_user_required/);
|
|
});
|
|
|
|
test("AI discovery update package declares all four route maps and passes the existing-service policy", () => {
|
|
const root = path.resolve(__dirname, "../..");
|
|
const request = JSON.parse(fs.readFileSync(path.join(root, "deployment", "requests", "AI-DISCOVERY-ICE-P-ROUTE-20260727.json")));
|
|
const checked = validateManifest(request, { requestId: request.request_id, commit });
|
|
assert.equal(checked.kind, "existing-service-update");
|
|
assert.deepEqual(checked.files.map(item => item.destination), [
|
|
"server.js",
|
|
"repository-route-map.json",
|
|
"server-node-map.json",
|
|
"fifth-domain-subject-registry.json",
|
|
"subject-id-alias-map.json",
|
|
]);
|
|
const unit = fs.readFileSync(path.join(root, request.unit_source), "utf8");
|
|
assert.equal(validateUpdateUnit(unit, request.module.run_user, request.module.install_root), unit);
|
|
for (const variable of ["GUANGHU_REPOSITORY_MAP", "GUANGHU_NODE_MAP", "GUANGHU_SUBJECT_REGISTRY", "GUANGHU_SUBJECT_ALIAS_MAP"]) {
|
|
assert.match(unit, new RegExp(`^Environment=${variable}=`, "m"));
|
|
}
|
|
});
|
|
|
|
test("provision copies only declared files and verifies loopback health", async () => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "architecture-provision-"));
|
|
const repoDir = path.join(root, "repo");
|
|
const releasesDir = path.join(root, "releases");
|
|
const unitDir = path.join(root, "units");
|
|
const receiptsDir = path.join(root, "receipts");
|
|
fs.mkdirSync(path.join(repoDir, "deployment", "requests"), { recursive: true });
|
|
fs.mkdirSync(path.join(repoDir, "server-tools", "example"), { recursive: true });
|
|
fs.writeFileSync(path.join(repoDir, "deployment", "requests", `${requestId}.json`), JSON.stringify(manifest()));
|
|
fs.writeFileSync(path.join(repoDir, "server-tools", "example", "server.js"), "module.exports = {};\n");
|
|
fs.writeFileSync(path.join(repoDir, "server-tools", "example", "example.service"), "[Service]\nUser=guanghu\nGroup=guanghu\nNoNewPrivileges=true\nPrivateTmp=true\nProtectSystem=strict\nProtectHome=true\nExecStart=/usr/bin/node __RELEASE_ROOT__/server-tools/example/server.js\n");
|
|
const commands = [];
|
|
let healthChecks = 0;
|
|
try {
|
|
const result = await provision({ target: "JD-FD-PRIMARY", action: "provision-approved-architecture", resource: `${requestId}@${commit}` }, {
|
|
repoDir, releasesDir, unitDir, receiptsDir,
|
|
run: async (file, args) => { commands.push([file, args]); return { stdout: args.includes("rev-parse") ? `${commit}\n` : "" }; },
|
|
getJson: async () => {
|
|
healthChecks += 1;
|
|
if (healthChecks === 1) throw new Error("connection_refused_during_startup");
|
|
return { ok: true, mode: "read-only" };
|
|
},
|
|
healthDelayMs: 0,
|
|
});
|
|
assert.equal(result.ok, true);
|
|
assert.equal(fs.existsSync(path.join(releasesDir, commit, "server-tools", "example", "server.js")), true);
|
|
assert.match(fs.readFileSync(path.join(unitDir, "example.service"), "utf8"), new RegExp(commit));
|
|
assert.equal(commands.some(([, args]) => args.includes("enable") && args.includes("--now")), true);
|
|
assert.equal(healthChecks, 2);
|
|
commands.length = 0;
|
|
const repeat = await provision({ target: "JD-FD-PRIMARY", action: "provision-approved-architecture", resource: `${requestId}@${commit}` }, {
|
|
repoDir, releasesDir, unitDir, receiptsDir,
|
|
run: async (file, args) => { commands.push([file, args]); return { stdout: args.includes("rev-parse") ? `${commit}\n` : "" }; },
|
|
getJson: async () => ({ ok: true, mode: "read-only" }),
|
|
healthDelayMs: 0,
|
|
});
|
|
assert.equal(repeat.ok, true);
|
|
assert.equal(commands.some(([, args]) => args[0] === "restart" && args[1] === "example.service"), true);
|
|
assert.equal(commands.some(([, args]) => args.includes("enable") && args.includes("--now")), false);
|
|
} finally { fs.rmSync(root, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test("existing service update backs up declared files, restarts, and verifies identity routes", async () => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "existing-service-update-"));
|
|
const repoDir = path.join(root, "repo"), releasesDir = path.join(root, "releases");
|
|
const unitDir = path.join(root, "units"), receiptsDir = path.join(root, "receipts");
|
|
const installRoot = path.join(root, "opt", "ai-discovery");
|
|
const updateRequestId = "AI-DISCOVERY-ICE-P-ROUTE-20260727";
|
|
const updateManifest = {
|
|
schema: "guanghu.existing-service-update-request/v1",
|
|
request_id: updateRequestId,
|
|
target_node: "JD-FD-PRIMARY",
|
|
status: "SERVICE_UPDATE_PACKAGE_READY · DEPLOYMENT_PENDING",
|
|
module: { unit: "guanghu-ai-discovery.service", run_user: "guanghu", install_root: "/opt/guanghu/ai-discovery" },
|
|
service_update: { kind: "existing-systemd-service", require_existing_unit: true, required_existing_files: ["server.js"] },
|
|
unit_source: "server-tools/ai-discovery/guanghu-ai-discovery.service",
|
|
files: [{ source: "server-tools/ai-discovery/server.js", destination: "server.js", mode: "0644" }],
|
|
runtime_check: { url: "http://127.0.0.1:3922/health", expected: { ok: true, mode: "read-only" } },
|
|
acceptance_checks: [{ url: "http://127.0.0.1:3922/v1/resolve?id=ICE-GL-ZY001", expected: { canonical_id: "ICE-P-ZY001", redirected: true } }],
|
|
};
|
|
fs.mkdirSync(path.join(repoDir, "deployment", "requests"), { recursive: true });
|
|
fs.mkdirSync(path.join(repoDir, "server-tools", "ai-discovery"), { recursive: true });
|
|
fs.mkdirSync(unitDir, { recursive: true });
|
|
fs.mkdirSync(installRoot, { recursive: true });
|
|
fs.writeFileSync(path.join(repoDir, "deployment", "requests", `${updateRequestId}.json`), JSON.stringify(updateManifest));
|
|
fs.writeFileSync(path.join(repoDir, "server-tools", "ai-discovery", "server.js"), "new server\n");
|
|
const newUnit = `[Service]\nUser=guanghu\nGroup=guanghu\nWorkingDirectory=${installRoot}\nNoNewPrivileges=true\nPrivateTmp=true\nProtectSystem=strict\nProtectHome=true\nReadOnlyPaths=${installRoot}\nExecStart=/usr/bin/node ${installRoot}/server.js\n`;
|
|
fs.writeFileSync(path.join(repoDir, "server-tools", "ai-discovery", "guanghu-ai-discovery.service"), newUnit);
|
|
fs.writeFileSync(path.join(installRoot, "server.js"), "old server\n");
|
|
fs.writeFileSync(path.join(unitDir, "guanghu-ai-discovery.service"), "old unit\n");
|
|
const commands = [];
|
|
try {
|
|
const result = await provision({ target: "JD-FD-PRIMARY", action: "provision-approved-architecture", resource: `${updateRequestId}@${commit}` }, {
|
|
repoDir, releasesDir, unitDir, receiptsDir, installRootOverride: installRoot,
|
|
run: async (file, args) => { commands.push([file, args]); return { stdout: args.includes("rev-parse") ? `${commit}\n` : "" }; },
|
|
getJson: async url => url.endsWith("/health") ? { ok: true, mode: "read-only" } : { canonical_id: "ICE-P-ZY001", redirected: true },
|
|
healthDelayMs: 0,
|
|
});
|
|
assert.equal(result.ok, true);
|
|
assert.equal(fs.readFileSync(path.join(installRoot, "server.js"), "utf8"), "new server\n");
|
|
assert.equal(commands.some(([, args]) => args[0] === "restart" && args[1] === "guanghu-ai-discovery.service"), true);
|
|
const receipt = JSON.parse(fs.readFileSync(path.join(receiptsDir, `${updateRequestId}.json`)));
|
|
assert.equal(receipt.result, "DEPLOYED_AND_VERIFIED");
|
|
assert.equal(receipt.acceptance_checks.length, 1);
|
|
} finally { fs.rmSync(root, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test("existing service update restores every changed file when acceptance fails", async () => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "existing-service-rollback-"));
|
|
const repoDir = path.join(root, "repo"), releasesDir = path.join(root, "releases");
|
|
const unitDir = path.join(root, "units"), receiptsDir = path.join(root, "receipts");
|
|
const installRoot = path.join(root, "opt", "ai-discovery");
|
|
const updateRequestId = "AI-DISCOVERY-ROLLBACK-TEST";
|
|
const updateManifest = {
|
|
schema: "guanghu.existing-service-update-request/v1",
|
|
request_id: updateRequestId,
|
|
target_node: "JD-FD-PRIMARY",
|
|
status: "SERVICE_UPDATE_PACKAGE_READY · DEPLOYMENT_PENDING",
|
|
module: { unit: "guanghu-ai-discovery.service", run_user: "guanghu", install_root: "/opt/guanghu/ai-discovery" },
|
|
service_update: { kind: "existing-systemd-service", require_existing_unit: true, required_existing_files: ["server.js"] },
|
|
unit_source: "server-tools/ai-discovery/guanghu-ai-discovery.service",
|
|
files: [
|
|
{ source: "server-tools/ai-discovery/server.js", destination: "server.js", mode: "0644" },
|
|
{ source: "identity/alias.json", destination: "alias.json", mode: "0644" }
|
|
],
|
|
runtime_check: { url: "http://127.0.0.1:3922/health", expected: { ok: true } },
|
|
acceptance_checks: [],
|
|
};
|
|
fs.mkdirSync(path.join(repoDir, "deployment", "requests"), { recursive: true });
|
|
fs.mkdirSync(path.join(repoDir, "server-tools", "ai-discovery"), { recursive: true });
|
|
fs.mkdirSync(path.join(repoDir, "identity"), { recursive: true });
|
|
fs.mkdirSync(unitDir, { recursive: true });
|
|
fs.mkdirSync(installRoot, { recursive: true });
|
|
fs.writeFileSync(path.join(repoDir, "deployment", "requests", `${updateRequestId}.json`), JSON.stringify(updateManifest));
|
|
fs.writeFileSync(path.join(repoDir, "server-tools", "ai-discovery", "server.js"), "new server\n");
|
|
fs.writeFileSync(path.join(repoDir, "identity", "alias.json"), "{}\n");
|
|
fs.writeFileSync(path.join(repoDir, "server-tools", "ai-discovery", "guanghu-ai-discovery.service"), `[Service]\nUser=guanghu\nGroup=guanghu\nWorkingDirectory=${installRoot}\nNoNewPrivileges=true\nPrivateTmp=true\nProtectSystem=strict\nProtectHome=true\nReadOnlyPaths=${installRoot}\nExecStart=/usr/bin/node ${installRoot}/server.js\n`);
|
|
fs.writeFileSync(path.join(installRoot, "server.js"), "old server\n");
|
|
fs.writeFileSync(path.join(unitDir, "guanghu-ai-discovery.service"), "old unit\n");
|
|
try {
|
|
const result = await provision({ target: "JD-FD-PRIMARY", action: "provision-approved-architecture", resource: `${updateRequestId}@${commit}` }, {
|
|
repoDir, releasesDir, unitDir, receiptsDir, installRootOverride: installRoot,
|
|
run: async (file, args) => ({ stdout: args.includes("rev-parse") ? `${commit}\n` : "" }),
|
|
getJson: async () => ({ ok: false }),
|
|
healthAttempts: 1,
|
|
healthDelayMs: 0,
|
|
});
|
|
assert.equal(result.ok, false);
|
|
assert.match(result.error, /runtime_check_failed:ok/);
|
|
assert.equal(fs.readFileSync(path.join(installRoot, "server.js"), "utf8"), "old server\n");
|
|
assert.equal(fs.existsSync(path.join(installRoot, "alias.json")), false);
|
|
assert.equal(fs.readFileSync(path.join(unitDir, "guanghu-ai-discovery.service"), "utf8"), "old unit\n");
|
|
} finally { fs.rmSync(root, { recursive: true, force: true }); }
|
|
});
|