guanghu-ice-heart/server-tools/lake-lamp-continuity-guard/finalize-development.mjs

639 lines
19 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import process from "node:process";
import { execFileSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const DEFAULT_GUARD =
"/Volumes/JZAO/HoloLake/persona-runtime/continuity-guard/scripts/continuity-lane.mjs";
const DEFAULT_STORE =
"/Volumes/JZAO/HoloLake/persona-runtime/continuity-memory/collaboration";
const CODE_CHANNEL_HOST = "guanghulab.com";
const CODE_CHANNEL_PREFIX = "/code/bingshuo/";
const CODE_CHANNEL_REPOSITORIES = new Set([
"guanghu-ice-heart",
"hololake-system-architecture",
"grok-build-upstream-mirror",
]);
const SAFE_CACHE_RELATIVE = [
/^target$/u,
/^coverage$/u,
/^\.nyc_output$/u,
/^\.pytest_cache$/u,
/^\.mypy_cache$/u,
/^\.ruff_cache$/u,
/^\.cache$/u,
/^\.next\/cache$/u,
/^node_modules\/\.cache$/u,
/(?:^|\/)__pycache__$/u,
];
const DEFAULT_CACHE_CANDIDATES = [
"target",
"coverage",
".nyc_output",
".pytest_cache",
".mypy_cache",
".ruff_cache",
".cache",
".next/cache",
"node_modules/.cache",
];
export function parseArgs(argv) {
const args = { receipts: [], cleanupPaths: [] };
for (let index = 0; index < argv.length; index += 1) {
const key = argv[index];
if (key === "--self-test") args.selfTest = true;
else if (key === "--receipt") args.receipts.push(argv[++index]);
else if (key === "--cleanup-path") args.cleanupPaths.push(argv[++index]);
else if (key.startsWith("--")) {
const name = key.slice(2).replaceAll(/-([a-z])/gu, (_, letter) =>
letter.toUpperCase(),
);
args[name] = argv[++index];
} else {
throw new Error(`UNKNOWN_ARGUMENT:${key}`);
}
}
return args;
}
export function normalizeCodeChannelRepository(value) {
let url;
try {
url = new URL(String(value || "").trim().replace(/^repo:\/\//u, "https://"));
} catch {
throw new Error("INVALID_CODE_CHANNEL_REPOSITORY");
}
if (url.username || url.password) {
throw new Error("CODE_CHANNEL_CREDENTIALS_FORBIDDEN");
}
const pathname = url.pathname.replace(/\.git$/u, "").replace(/\/+$/u, "");
const slug = pathname.startsWith(CODE_CHANNEL_PREFIX)
? pathname.slice(CODE_CHANNEL_PREFIX.length)
: "";
if (
url.protocol !== "https:" ||
url.hostname !== CODE_CHANNEL_HOST ||
!CODE_CHANNEL_REPOSITORIES.has(slug)
) {
throw new Error("NON_CANONICAL_CODE_CHANNEL_REPOSITORY");
}
return {
identity: `repo://${CODE_CHANNEL_HOST}${CODE_CHANNEL_PREFIX}${slug}`,
cloneUrl: `https://${CODE_CHANNEL_HOST}${CODE_CHANNEL_PREFIX}${slug}.git`,
slug,
};
}
export function normalizeBranch(value) {
const branch = String(value || "").trim();
if (
!branch ||
!/^(?!\/)(?!.*(?:\.\.|\/\/|@\{|[~^:?*\[\\]))[A-Za-z0-9._/-]+$/u.test(
branch,
) ||
branch.endsWith("/") ||
branch.endsWith(".")
) {
throw new Error("INVALID_TARGET_BRANCH");
}
return branch;
}
export function normalizeReceipt(value) {
const relative = String(value || "").replaceAll("\\", "/").replace(/^\.\/+/u, "");
if (
!relative.startsWith("deployment/receipts/") ||
relative.includes("/../") ||
relative.endsWith("/") ||
!/^[A-Za-z0-9._/-]+$/u.test(relative)
) {
throw new Error("INVALID_DEPLOYMENT_RECEIPT_PATH");
}
return relative;
}
export function safeCacheRelative(worktree, candidate) {
const root = fs.realpathSync(worktree);
const absolute = path.resolve(root, candidate);
const relative = path.relative(root, absolute).replaceAll(path.sep, "/");
if (
!relative ||
relative.startsWith("../") ||
path.isAbsolute(relative) ||
!SAFE_CACHE_RELATIVE.some((pattern) => pattern.test(relative))
) {
throw new Error(`CACHE_PATH_NOT_ALLOWLISTED:${candidate}`);
}
return { absolute, relative };
}
export function isJsonRecordName(name) {
return String(name).endsWith(".json") && !String(name).startsWith("._");
}
function run(file, args, options = {}) {
return execFileSync(file, args, {
cwd: options.cwd,
encoding: "utf8",
stdio: options.stdio || ["ignore", "pipe", "pipe"],
maxBuffer: 16 * 1024 * 1024,
}).trim();
}
function git(worktree, args) {
return run("/usr/bin/git", ["-C", worktree, ...args]);
}
function guard(guardScript, args) {
const output = run("/usr/bin/node", [guardScript, ...args]);
return output ? JSON.parse(output) : null;
}
function readJson(filePath) {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
}
function sha256(value) {
return crypto.createHash("sha256").update(value).digest("hex");
}
function listOwnedLeases(storeRoot, developmentId) {
const directory = path.join(storeRoot, "leases");
if (!fs.existsSync(directory)) return [];
return fs
.readdirSync(directory)
.filter(isJsonRecordName)
.map((name) => readJson(path.join(directory, name)))
.filter((lease) => lease.development_id === developmentId);
}
function ensureLane(storeRoot, developmentId, worktree) {
const lanePath = path.join(storeRoot, "lanes", `${developmentId}.json`);
const lane = readJson(lanePath);
if (lane.status !== "active" && lane.status !== "waiting") {
throw new Error(`LANE_NOT_RUNNABLE:${lane.status}`);
}
const owned = listOwnedLeases(storeRoot, developmentId);
const resolvedWorktree = fs.realpathSync(worktree);
if (
!owned.some((lease) => {
if (!path.isAbsolute(lease.resource)) return false;
try {
const resource = fs.realpathSync(lease.resource);
return (
resolvedWorktree === resource ||
resolvedWorktree.startsWith(`${resource}${path.sep}`) ||
resource.startsWith(`${resolvedWorktree}${path.sep}`)
);
} catch {
return false;
}
})
) {
throw new Error("WORKTREE_WRITE_LEASE_REQUIRED");
}
return lane;
}
function verifyWorktree(worktree, receipts) {
const root = fs.realpathSync(git(worktree, ["rev-parse", "--show-toplevel"]));
if (root !== fs.realpathSync(worktree)) {
throw new Error("WORKTREE_MUST_BE_GIT_ROOT");
}
if (git(root, ["status", "--porcelain=v1"])) {
throw new Error("WORKTREE_NOT_CLEAN");
}
const head = git(root, ["rev-parse", "HEAD"]);
if (!/^[0-9a-f]{40}$/u.test(head)) throw new Error("INVALID_LOCAL_HEAD");
const branch = normalizeBranch(git(root, ["branch", "--show-current"]));
const receiptEvidence = receipts.map((receipt) => {
const relative = normalizeReceipt(receipt);
const content = git(root, ["show", `HEAD:${relative}`]);
if (!content) throw new Error(`RECEIPT_NOT_COMMITTED:${relative}`);
return {
path: relative,
sha256: sha256(Buffer.from(content)),
bytes: Buffer.byteLength(content),
};
});
return { root, head, branch, receiptEvidence };
}
function startPublish({
guardScript,
developmentId,
repository,
branch,
}) {
const requested = guard(guardScript, [
"publish-request",
"--development-id",
developmentId,
"--repository",
repository.identity,
"--target",
branch,
"--priority",
"normal",
]);
guard(guardScript, ["publish-dispatch"]);
guard(guardScript, [
"publish-start",
"--development-id",
developmentId,
"--request-id",
requested.request_id,
]);
return requested.request_id;
}
function finishPublish(guardScript, developmentId, requestId, outcome, receipt) {
return guard(guardScript, [
"publish-finish",
"--development-id",
developmentId,
"--request-id",
requestId,
"--outcome",
outcome,
"--receipt",
receipt,
]);
}
function pushAndReadBack(worktree, repository, branch, head, receipts) {
run("/usr/bin/git", [
"-C",
worktree,
"fetch",
"--quiet",
"origin",
`refs/heads/${branch}`,
]);
const remoteBefore = git(worktree, [
"rev-parse",
`refs/remotes/origin/${branch}`,
]);
try {
git(worktree, ["merge-base", "--is-ancestor", remoteBefore, head]);
} catch {
throw new Error(`NON_FAST_FORWARD_REFUSED:${remoteBefore}`);
}
run("/usr/bin/git", [
"-C",
worktree,
"push",
"origin",
`HEAD:refs/heads/${branch}`,
]);
const remoteLine = run("/usr/bin/git", [
"ls-remote",
repository.cloneUrl,
`refs/heads/${branch}`,
]);
const remoteHead = remoteLine.split(/\s+/u)[0] || "";
if (remoteHead !== head) {
throw new Error(`REMOTE_HEAD_MISMATCH:${remoteHead || "missing"}`);
}
const readbackRoot = fs.mkdtempSync(
path.join(os.tmpdir(), "hololake-finalize-readback-"),
);
try {
const clone = path.join(readbackRoot, "repo");
run("/usr/bin/git", [
"clone",
"--quiet",
"--depth",
"1",
"--branch",
branch,
repository.cloneUrl,
clone,
]);
const cloneHead = git(clone, ["rev-parse", "HEAD"]);
if (cloneHead !== head) throw new Error(`FRESH_CLONE_HEAD_MISMATCH:${cloneHead}`);
git(clone, ["fsck", "--full"]);
for (const receipt of receipts) {
const remoteContent = git(clone, ["show", `HEAD:${receipt.path}`]);
if (sha256(Buffer.from(remoteContent)) !== receipt.sha256) {
throw new Error(`REMOTE_RECEIPT_MISMATCH:${receipt.path}`);
}
}
} finally {
fs.rmSync(readbackRoot, { recursive: true, force: true });
}
return { remoteBefore, remoteHead };
}
function activeProcessFor(absolute) {
const output = run("/bin/ps", ["-axo", "pid=,command="]);
return output
.split("\n")
.map((line) => line.trim())
.filter(Boolean)
.some((line) => {
const match = line.match(/^(\d+)\s+(.*)$/u);
if (!match) return false;
const pid = Number(match[1]);
return pid !== process.pid && pid !== process.ppid && match[2].includes(absolute);
});
}
function directoryBytes(root) {
let total = 0;
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
const candidate = path.join(root, entry.name);
const stat = fs.lstatSync(candidate);
if (stat.isSymbolicLink()) continue;
if (stat.isDirectory()) total += directoryBytes(candidate);
else if (stat.isFile()) total += stat.size;
}
return total;
}
function collectPythonCaches(root, limit = 256) {
const found = [];
const walk = (directory, depth) => {
if (depth > 6 || found.length >= limit) return;
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
if (found.length >= limit) return;
if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
const candidate = path.join(directory, entry.name);
if (entry.name === "__pycache__") {
found.push(path.relative(root, candidate));
continue;
}
if ([".git", "node_modules", "target", "build", "dist"].includes(entry.name)) {
continue;
}
walk(candidate, depth + 1);
}
};
walk(root, 0);
return found;
}
function cleanupCaches(worktree, explicitPaths) {
const candidates = [
...DEFAULT_CACHE_CANDIDATES,
...collectPythonCaches(worktree),
...explicitPaths,
];
const unique = new Map();
for (const candidate of candidates) {
const safe = safeCacheRelative(worktree, candidate);
unique.set(safe.absolute, safe);
}
const results = [];
for (const item of unique.values()) {
if (!fs.existsSync(item.absolute)) {
results.push({ path: item.relative, result: "SKIPPED_NOT_PRESENT", bytes: 0 });
continue;
}
const stat = fs.lstatSync(item.absolute);
if (stat.isSymbolicLink() || !stat.isDirectory()) {
throw new Error(`CACHE_TARGET_NOT_REAL_DIRECTORY:${item.relative}`);
}
if (git(worktree, ["ls-files", "--", item.relative])) {
throw new Error(`TRACKED_CACHE_REFUSED:${item.relative}`);
}
try {
git(worktree, ["check-ignore", "--no-index", "-q", "--", item.relative]);
} catch {
throw new Error(`UNIGNORED_CACHE_REFUSED:${item.relative}`);
}
if (activeProcessFor(item.absolute)) {
results.push({ path: item.relative, result: "SKIPPED_ACTIVE", bytes: 0 });
continue;
}
const bytes = directoryBytes(item.absolute);
fs.rmSync(item.absolute, { recursive: true, force: true });
results.push({ path: item.relative, result: "REMOVED", bytes });
}
return results;
}
function writeFinalizationReceipt(storeRoot, developmentId, payload) {
const directory = path.join(storeRoot, "finalization-receipts");
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
const filePath = path.join(
directory,
`${developmentId}-${new Date().toISOString().replaceAll(/[:.]/gu, "-")}.json`,
);
const temporary = `${filePath}.tmp-${process.pid}`;
fs.writeFileSync(temporary, `${JSON.stringify(payload, null, 2)}\n`, {
mode: 0o600,
});
fs.renameSync(temporary, filePath);
return filePath;
}
function releaseAndComplete({
guardScript,
storeRoot,
developmentId,
summary,
}) {
for (const lease of listOwnedLeases(storeRoot, developmentId)) {
guard(guardScript, [
"release",
"--development-id",
developmentId,
"--resource",
lease.resource,
]);
}
const lane = guard(guardScript, [
"complete",
"--development-id",
developmentId,
"--summary",
summary,
]);
const actions = guard(guardScript, ["completion-actions"]);
const current = actions.find(
(item) => item.development_id === developmentId,
);
if (!current) throw new Error("COMPLETION_ACTION_MISSING");
if ((current.delete_automation_ids || []).length === 0) {
guard(guardScript, [
"mark-completion-stopped",
"--development-id",
developmentId,
]);
return { lane_status: lane.status, notification_state: "stopped" };
}
return {
lane_status: lane.status,
notification_state: "stop_requested",
delete_automation_ids: current.delete_automation_ids,
};
}
export async function finalize(options) {
const developmentId = String(options.developmentId || "");
if (!/^[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+$/u.test(developmentId)) {
throw new Error("INVALID_DEVELOPMENT_ID");
}
const guardScript = options.guardScript || DEFAULT_GUARD;
const storeRoot = options.storeRoot || DEFAULT_STORE;
const worktree = fs.realpathSync(options.worktree || "");
if (!options.receipts?.length) throw new Error("AT_LEAST_ONE_RECEIPT_REQUIRED");
ensureLane(storeRoot, developmentId, worktree);
const verified = verifyWorktree(worktree, options.receipts);
const repository = normalizeCodeChannelRepository(
options.repository || git(worktree, ["config", "--get", "remote.origin.url"]),
);
const branch = normalizeBranch(options.target || verified.branch);
if (branch !== verified.branch) throw new Error("CHECKED_OUT_BRANCH_MISMATCH");
let publishRequestId = null;
let publishStarted = false;
try {
publishRequestId = startPublish({
guardScript,
developmentId,
repository,
branch,
});
publishStarted = true;
const remote = pushAndReadBack(
worktree,
repository,
branch,
verified.head,
verified.receiptEvidence,
);
const publishReceipt =
`PASS_100 remote=${repository.identity}#${branch} ` +
`head=${remote.remoteHead} receipts=${verified.receiptEvidence
.map((item) => `${item.path}:${item.sha256}`)
.join(",")}`;
finishPublish(
guardScript,
developmentId,
publishRequestId,
"completed",
publishReceipt,
);
publishStarted = false;
const cleanup = cleanupCaches(worktree, options.cleanupPaths || []);
const removedBytes = cleanup
.filter((item) => item.result === "REMOVED")
.reduce((total, item) => total + item.bytes, 0);
const payload = {
schema: "hololake.development-finalization-receipt/v1",
development_id: developmentId,
result: "PASS_100",
code_channel: {
repository: repository.identity,
branch,
remote_head: remote.remoteHead,
fresh_clone_readback: 100,
git_fsck: 100,
receipts: verified.receiptEvidence,
},
cleanup: {
scope: "CURRENT_WORKTREE_IGNORED_ALLOWLISTED_CACHES_ONLY",
removed_bytes: removedBytes,
items: cleanup,
},
recorded_at: new Date().toISOString(),
};
const finalizationReceipt = writeFinalizationReceipt(
storeRoot,
developmentId,
payload,
);
const completion = releaseAndComplete({
guardScript,
storeRoot,
developmentId,
summary:
`自动收口PASS_100第五域代码频道${repository.identity}#${branch}` +
`远端回读${remote.remoteHead}${verified.receiptEvidence.length}份回执一致;` +
`本车道安全缓存清理${removedBytes}字节;回执${finalizationReceipt}`,
});
return { ...payload, finalization_receipt: finalizationReceipt, completion };
} catch (error) {
if (publishStarted && publishRequestId) {
try {
finishPublish(
guardScript,
developmentId,
publishRequestId,
"failed",
`FAIL_0 ${String(error.message || error).slice(0, 400)}`,
);
} catch {
// The original finalization failure remains authoritative.
}
}
throw error;
}
}
export function selfTest() {
const canonical = normalizeCodeChannelRepository(
"https://guanghulab.com/code/bingshuo/guanghu-ice-heart.git",
);
if (
canonical.identity !==
"repo://guanghulab.com/code/bingshuo/guanghu-ice-heart"
) {
throw new Error("SELF_TEST_REPOSITORY_NORMALIZATION_FAILED");
}
for (const invalid of [
"https://guanghulab.com/fifth-domain/bingshuo/fifth-domain.git",
"https://user:secret@guanghulab.com/code/bingshuo/guanghu-ice-heart.git",
]) {
let rejected = false;
try {
normalizeCodeChannelRepository(invalid);
} catch {
rejected = true;
}
if (!rejected) throw new Error("SELF_TEST_INVALID_REPOSITORY_ACCEPTED");
}
if (
normalizeReceipt(
"deployment/receipts/GLW-CHJH-ROUTER-CONTROLLER-JD-20260805.json",
) !==
"deployment/receipts/GLW-CHJH-ROUTER-CONTROLLER-JD-20260805.json"
) {
throw new Error("SELF_TEST_RECEIPT_NORMALIZATION_FAILED");
}
return { result: "PASS_100" };
}
async function main() {
const args = parseArgs(process.argv.slice(2));
const result = args.selfTest
? selfTest()
: await finalize({
developmentId: args.developmentId,
worktree: args.worktree,
repository: args.repository,
target: args.target,
receipts: args.receipts,
cleanupPaths: args.cleanupPaths,
guardScript: args.guardScript,
storeRoot: args.storeRoot,
});
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
main().catch((error) => {
process.stderr.write(`FINALIZATION_FAIL_0:${String(error.message || error)}\n`);
process.exitCode = 1;
});
}