fix: separate Fifth Domain source deployment identities

This commit is contained in:
冰朔 2026-07-26 17:56:47 +08:00
commit 83589aa2c2
27 changed files with 620 additions and 73 deletions

View file

@ -60,6 +60,7 @@ function createApp(options = {}) {
});
const repoGrantDir = options.repoGrantDir || process.env.LAKE_LAMP_REPO_GRANT_DIR || "/var/lib/guanghu/repo-authorizations";
const deploymentQueueDir = options.deploymentQueueDir || process.env.LAKE_LAMP_DEPLOYMENT_EVENT_DIR || "/var/lib/guanghu/deployment-events";
const deploymentRegistryFile = options.deploymentRegistryFile || process.env.LAKE_LAMP_DEPLOYMENT_REPOSITORIES || "/etc/guanghu/lake-lamp/deployment-repositories.json";
const executeAction = options.executeAction || executeRegisteredAction;
// Creating a powerless request must never become harder than the human mail
// handoff. Keep at least three attempts per network each hour.
@ -81,6 +82,10 @@ function createApp(options = {}) {
manager.failApprovalEmail(handoffToken);
return { ok: false, reason: "no_registered_approver" };
}
if (!manager.bindApprover(handoffToken, approver.id)) {
manager.failApprovalEmail(handoffToken);
return { ok: false, reason: "approver_binding_failed" };
}
const approvalUrl = `${publicBaseUrl}/approve/${issued.approvalToken}`;
const emailSent = await sendEmail({
to: approver.email,
@ -325,7 +330,23 @@ function createApp(options = {}) {
if (!verified.ok) return json(res, 403, failure(verified.reason));
const map = mapGate.read(target);
if (!mapGate.verify(token, target, map.hash).ok) return json(res, 423, failure("map_ack_required", "先读取并确认导航图。", { required_action: "read-navigation-map" }));
const queued = enqueueDeploymentEvent({ schema: "guanghu.deployment-intent/v1", repo, branch, commit_sha: commit, resource, manifest, workorder_id: verified.session.workorderId }, { repo, branch, commit_sha: commit }, deploymentQueueDir);
const deploymentRepositories = options.deploymentRepositories || loadDeploymentRepositories(deploymentRegistryFile);
const queued = enqueueDeploymentEvent({
schema: "guanghu.deployment-intent/v1",
repo,
branch,
commit_sha: commit,
resource,
manifest,
workorder_id: verified.session.workorderId,
deployment_source: body.deployment_source || null,
}, { repo, branch, commit_sha: commit }, deploymentQueueDir, {
authorizer_id: verified.session.authorizerId,
persona_id: verified.session.persona.pid,
execution_runtime_id: String(body.execution_runtime_id || ""),
target,
registry: deploymentRepositories,
});
const operationReceipt = receipt({ state: queued.state === "queued_for_resident_agent" ? "queued" : "blocked", diagnostic_code: queued.diagnostic_code || "deployment_event_queued", workorder_id: verified.session.workorderId, target, action: "dispatch-approved-deployment", evidence: { repo, branch, commit_sha: commit, event_id: queued.event_id || "" }, next_step: queued.state === "queued_for_resident_agent" ? "常驻部署 Agent 将读取该事件并回写部署、健康检查或回滚回执。" : "修正部署绑定信息后重新申请或派发,不要让服务器自行扫描提交。" });
manager.recordReceipt(token, operationReceipt);
return json(res, queued.state === "queued_for_resident_agent" ? 202 : 400, { ok: queued.state === "queued_for_resident_agent", deployment: queued, receipt: operationReceipt });
@ -339,6 +360,12 @@ function createApp(options = {}) {
});
}
function loadDeploymentRepositories(file) {
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
if (!parsed || !parsed.repos || typeof parsed.repos !== "object") throw new Error("invalid_deployment_repository_registry");
return parsed.repos;
}
function approvalPage(order, token) {
return document("小湖灯授权请求", `
<p class="eyebrow">LAKE LAMP SECURITY PROTOCOL</p>
@ -416,7 +443,7 @@ function safeEqual(left, right) { const a = Buffer.from(String(left)); const b =
function sha256(value) { return crypto.createHash("sha256").update(String(value)).digest("hex"); }
function splitCsv(value) { return value.split(",").map(item => item.trim()).filter(Boolean); }
function loadApprovers(file, ownerEmail) {
if (!file) return ownerEmail ? [{ id: "sovereign-owner", email: ownerEmail, default: true, persona_ids: [], targets: ["*"], scopes: ["*"] }] : [];
if (!file) return ownerEmail ? [{ id: "ICE-GL∞", email: ownerEmail, default: true, persona_ids: [], targets: ["*"], scopes: ["*"] }] : [];
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
if (!parsed || !Array.isArray(parsed.approvers)) throw new Error("invalid approver registry");
return parsed.approvers.filter(item => item && validEmail(item.email)).map(item => ({