diff --git a/deployment/HLCC-PUSH-TO-DEPLOY-EVENT-PROTOCOL.hdlp b/deployment/HLCC-PUSH-TO-DEPLOY-EVENT-PROTOCOL.hdlp index beaa5cb..9043118 100644 --- a/deployment/HLCC-PUSH-TO-DEPLOY-EVENT-PROTOCOL.hdlp +++ b/deployment/HLCC-PUSH-TO-DEPLOY-EVENT-PROTOCOL.hdlp @@ -1,6 +1,6 @@ # HLCC-PUSH-TO-DEPLOY-EVENT-PROTOCOL -状态:本地实现候选,尚未部署;当前现实层不得宣称可派发 +状态:本地实现候选,尚未部署 语言层在冰朔明确确认“该提交需要部署”后,主动发出 `guanghu.deployment-intent/v1`。推送本身不会产生部署事件。意图必须绑定: @@ -11,9 +11,3 @@ 语言人格体通过 `/api/deployment/dispatch` 派发这一意图;接口要求一张单独的 `server-ops / dispatch-approved-deployment` 工单,且资源必须为同一 `REQUEST-ID@完整提交SHA`。服务端才写入 `guanghu.deployment-event/v1` 到本机队列。常驻部署 Agent 订阅此队列,读取同一提交中的清单,执行备份、部署、健康检查与回滚,并将回执回写到原工单。 没有部署意图的提交照常入库,回执为 `not_requested`;无效意图回执为 `rejected`,不会部署。推送工单绝不扩大为部署权限;服务器也不扫描仓库主动拉取部署。 - -`resource` 中的 `REQUEST-ID` 必须与清单文件名完全一致:`deployment/requests/.json`。批准 A 而执行 B 必须在派发端和常驻 Agent 端分别拒绝。 - -首次新单元使用 `guanghu.architecture-provision-request/v1`;已经在线的 systemd 服务必须使用 `guanghu.existing-service-update-request/v1`,先完整备份声明目标,再重启和逐项验收,失败恢复全部旧文件与旧单元。二者不得互相冒充。 - -当前京东节点若尚未安装 `/api/deployment/dispatch`、事件队列和常驻 worker,则先通过已有的主人邮件批准固定管理动作引导这套运行时。没有这个固定引导动作时应回执 `BOOTSTRAP_ACTION_NOT_DEPLOYED`,不得绕回人类 SSH 或云控制台,也不得发送一张服务器根本不会执行的部署工单。 diff --git a/deployment/requests/AI-DISCOVERY-ICE-P-ROUTE-20260727.json b/deployment/requests/AI-DISCOVERY-ICE-P-ROUTE-20260727.json deleted file mode 100644 index ed5d22f..0000000 --- a/deployment/requests/AI-DISCOVERY-ICE-P-ROUTE-20260727.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "schema": "guanghu.existing-service-update-request/v1", - "request_id": "AI-DISCOVERY-ICE-P-ROUTE-20260727", - "target_node": "JD-FD-PRIMARY", - "status": "SERVICE_UPDATE_PACKAGE_READY · DEPLOYMENT_PENDING", - "purpose": { - "what": "更新已在线的光湖 AI 发现服务,使公开只读入口加载完整第五域仓库、节点、主体与旧编号别名地图。", - "why": "代码频道已经把铸渊规范编号统一为 ICE-P-ZY001,但京东主节点仍运行旧服务,旧编号无法自动导航到规范编号,仓库事实尚未进入现实运行层。", - "success": "公开入口可读取主体注册表,并将 ICE-GL-ZY001 与 ICE-PZY-001 解析为 ICE-P-ZY001;REPO-012 与现有健康接口继续正常。", - "out_of_scope": [ - "不修改人类登录入口或 SSH", - "不修改广州前门与现有隧道", - "不部署企业应用栈", - "不授予任何旧编号权限" - ] - }, - "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-gateway/guanghu-ai-discovery.service", - "files": [ - { - "source": "server-tools/ai-discovery-gateway/server.js", - "destination": "server.js", - "mode": "0644" - }, - { - "source": "routing/repository-route-map.json", - "destination": "repository-route-map.json", - "mode": "0644" - }, - { - "source": "routing/server-node-map.json", - "destination": "server-node-map.json", - "mode": "0644" - }, - { - "source": "identity/fifth-domain-subject-registry.json", - "destination": "fifth-domain-subject-registry.json", - "mode": "0644" - }, - { - "source": "identity/subject-id-alias-map.json", - "destination": "subject-id-alias-map.json", - "mode": "0644" - } - ], - "runtime_check": { - "url": "http://127.0.0.1:3922/health", - "expected": { - "ok": true, - "service": "guanghu-ai-discovery", - "mode": "read-only" - } - }, - "acceptance_checks": [ - { - "url": "http://127.0.0.1:3922/v1/subjects", - "expected": { - "schema": "guanghu.fifth-domain-subject-registry/v1" - } - }, - { - "url": "http://127.0.0.1:3922/v1/resolve?id=ICE-GL-ZY001", - "expected": { - "status": "RESOLVED", - "canonical_id": "ICE-P-ZY001", - "redirected": true - } - }, - { - "url": "http://127.0.0.1:3922/v1/resolve?id=ICE-PZY-001", - "expected": { - "status": "RESOLVED", - "canonical_id": "ICE-P-ZY001", - "redirected": true - } - }, - { - "url": "http://127.0.0.1:3922/v1/resolve?id=REPO-012", - "expected": { - "code": "REPO-012", - "state": "CURRENT_FIFTH_DOMAIN_ENTRY" - } - } - ], - "post_deployment_observation": "常驻 Agent 完成本机强校验后,语言层必须再读取公开 /api/ai 对应接口;公开路径不一致时不得宣称整条链完成。", - "rollback": { - "automatic_on_failure": true, - "scope": "restore every declared destination and the previous systemd unit, then restart the previous service", - "receipt_required": true - }, - "security": { - "public_read_only": true, - "no_secrets": true, - "legacy_ids_are_navigation_aliases_only": true, - "human_login_state_unchanged": true - } -} diff --git a/deployment/requests/README.md b/deployment/requests/README.md index cfc180f..4834bb7 100644 --- a/deployment/requests/README.md +++ b/deployment/requests/README.md @@ -1,11 +1,6 @@ -# 部署请求 +# 新架构首次部署请求 -本目录同时容纳两类严格区分的清单: - -- `guanghu.architecture-provision-request/v1`:服务器上尚不存在的新架构单元首次进入现实层; -- `guanghu.existing-service-update-request/v1`:更新已经存在的 systemd 服务,必须先备份全部声明目标,重启后做回环健康检查,失败则恢复全部文件和旧单元。 - -不得用“首次部署”清单替代已有服务升级,也不得把升级失败降级为继续运行未验证的新文件。 +本目录中的 `guanghu.architecture-provision-request/v1` 文件是新系统架构第一次进入服务器的结构化清单。 固定流程: diff --git a/server-tools/ai-discovery-gateway/guanghu-ai-discovery.service b/server-tools/ai-discovery-gateway/guanghu-ai-discovery.service index 69f5364..61010d6 100644 --- a/server-tools/ai-discovery-gateway/guanghu-ai-discovery.service +++ b/server-tools/ai-discovery-gateway/guanghu-ai-discovery.service @@ -11,9 +11,6 @@ WorkingDirectory=/opt/guanghu/ai-discovery Environment=GUANGHU_AI_HOST=127.0.0.1 Environment=GUANGHU_AI_PORT=3922 Environment=GUANGHU_REPOSITORY_MAP=/opt/guanghu/ai-discovery/repository-route-map.json -Environment=GUANGHU_NODE_MAP=/opt/guanghu/ai-discovery/server-node-map.json -Environment=GUANGHU_SUBJECT_REGISTRY=/opt/guanghu/ai-discovery/fifth-domain-subject-registry.json -Environment=GUANGHU_SUBJECT_ALIAS_MAP=/opt/guanghu/ai-discovery/subject-id-alias-map.json ExecStart=/usr/bin/node /opt/guanghu/ai-discovery/server.js Restart=always RestartSec=5 diff --git a/server-tools/lake-lamp-authz/README.md b/server-tools/lake-lamp-authz/README.md index d076545..3545145 100644 --- a/server-tools/lake-lamp-authz/README.md +++ b/server-tools/lake-lamp-authz/README.md @@ -102,27 +102,23 @@ node server-tools/lake-lamp-authz/authorize-repo-push.js \ - `restore-code-channel-owner-login` 只把京东本机旧第五域数据库中的 `bingshuo` 密码摘要恢复到新代码频道,同时校正启用、管理员和禁止登录状态。它不读取明文密码,不修改 SSH,并在变更前使用 SQLite backup API 生成一致性备份。 - 登录问题先执行 `inspect-code-channel-owner-auth`。只有确认新旧频道凭证不一致时,才申请后一项恢复动作;不得绕到新加坡灾备节点,不得向冰朔索要密码。 -## 首次部署与已有服务更新 +## 新架构首次部署 旧的 `deploy-registered-service` 只能操作已经登记的服务,不能承担首次安装。新架构统一使用固定动作 `provision-approved-architecture`,并把仓库请求编号与不可变提交绑定进工单: ```bash node request-workorder.js \ --url https://guanghulab.com/authz \ - --persona ICE-P-ZY001 \ + --persona ICE-GL-ZY001 \ --name 铸渊 \ --target JD-FD-PRIMARY \ --scope server-ops \ --action provision-approved-architecture \ --resource 'REQUEST-ID@40位提交SHA' \ - --description '安装或更新已审核的不可变部署包' + --description '首次安装已审核架构包' ``` -邮件或可信对话签字页面必须显示同一个 `resource`。批准会话不能切换请求编号或提交,清单路径也必须严格等于 `deployment/requests/.json`。 - -首次部署执行器只复制清单列出的普通文件,只安装清单指定的非 root、加固 systemd 单元,并只接受回环健康检查。人格体可以使用清单声明的独立低权限账户、共享模型密钥文件和状态目录;密钥路径必须位于 `/etc/guanghu/persona-secrets/`,可写路径必须位于 `/var/lib/guanghu/personas/<运行账户>/`。 - -已有服务更新必须使用 `guanghu.existing-service-update-request/v1`,不得伪装成首次部署。执行器先确认旧单元和必需旧文件存在,再备份单元及每个声明目标,只写入清单列出的 `/opt/guanghu/<服务>/` 文件,重启原单元并逐项执行回环验收;任一检查失败即恢复全部旧文件和旧单元。说明文字不能改变部署内容,也不开放任意 shell。 +邮件或可信对话签字页面必须显示同一个 `resource`。批准会话不能切换请求编号或提交。执行器只读取该提交中 `deployment/requests/.json`,只复制清单列出的普通文件,只安装清单指定的非 root、加固 systemd 单元,并只接受回环健康检查。人格体可以使用清单声明的独立低权限账户、共享模型密钥文件和状态目录;密钥路径必须位于 `/etc/guanghu/persona-secrets/`,可写路径必须位于 `/var/lib/guanghu/personas/<运行账户>/`。覆盖旧单元前强制备份,启动或验收失败时自动恢复。说明文字不能改变部署内容,也不开放任意 shell。 这套入口本身需要在京东主控上一次性安装: @@ -130,4 +126,4 @@ node request-workorder.js \ sudo bash server-tools/lake-lamp-authz/install-architecture-provisioner.sh ``` -引导本身也是一次受控部署:应从已经存在的主人邮件批准系统管理通道执行,不得为此恢复人类 SSH 登录。若当前服务器尚未登记能够安装此运行时的固定动作,状态必须记为 `BOOTSTRAP_ACTION_NOT_DEPLOYED`,不能把仓库提交、工单批准或打开云后台冒充成运行时已部署。引导完成并读回三个服务均为 active 后,未来安装与更新才走通感桥的结构化工单。 +这是最后一次需要云厂商控制台或现有系统管理通道的引导。安装完成后,未来新架构均走上面的结构化工单,不必预先把每个未来模块写进旧动作桥。 diff --git a/server-tools/lake-lamp-authz/architecture-provision-broker.js b/server-tools/lake-lamp-authz/architecture-provision-broker.js index afd7052..c08857c 100644 --- a/server-tools/lake-lamp-authz/architecture-provision-broker.js +++ b/server-tools/lake-lamp-authz/architecture-provision-broker.js @@ -24,59 +24,19 @@ function safeRelative(value) { } function validateManifest(manifest, resource) { - if (!manifest || !["guanghu.architecture-provision-request/v1", "guanghu.existing-service-update-request/v1"].includes(manifest.schema)) throw new Error("invalid_manifest_schema"); + if (!manifest || manifest.schema !== "guanghu.architecture-provision-request/v1") throw new Error("invalid_manifest_schema"); if (manifest.request_id !== resource.requestId || manifest.target_node !== "JD-FD-PRIMARY") throw new Error("manifest_identity_mismatch"); - const unit = String(manifest.module && manifest.module.unit || ""); - if (!/^[A-Za-z0-9_.@-]+\.service$/.test(unit)) throw new Error("invalid_unit_name"); - if (manifest.schema === "guanghu.existing-service-update-request/v1") return validateServiceUpdateManifest(manifest, unit); if (manifest.status !== "ARCHITECTURE_PACKAGE_READY · INITIAL_PROVISION_PENDING") throw new Error("manifest_not_pending"); if (!manifest.initial_provision || manifest.initial_provision.kind !== "new-architecture-unit") throw new Error("not_initial_architecture_unit"); + const unit = String(manifest.module && manifest.module.unit || ""); + if (!/^[A-Za-z0-9_.@-]+\.service$/.test(unit)) throw new Error("invalid_unit_name"); if (!Array.isArray(manifest.source_paths) || manifest.source_paths.length < 1 || manifest.source_paths.length > 64 || manifest.source_paths.some(item => !safeRelative(item))) throw new Error("invalid_source_paths"); const unitMatches = manifest.source_paths.filter(item => path.basename(item) === unit); if (unitMatches.length !== 1) throw new Error("unit_not_uniquely_declared"); const check = manifest.runtime_check || {}; if (!/^http:\/\/127\.0\.0\.1:\d{2,5}\/[A-Za-z0-9._/?=&-]*$/.test(String(check.url || ""))) throw new Error("invalid_loopback_runtime_check"); if (!check.expected || typeof check.expected !== "object" || Array.isArray(check.expected)) throw new Error("invalid_runtime_expectation"); - return { kind: "initial-provision", unit, unitSource: unitMatches[0], sourcePaths: manifest.source_paths, runtimeCheck: check }; -} - -function validateServiceUpdateManifest(manifest, unit) { - if (manifest.status !== "SERVICE_UPDATE_PACKAGE_READY · DEPLOYMENT_PENDING") throw new Error("manifest_not_pending"); - if (!manifest.service_update || manifest.service_update.kind !== "existing-systemd-service" || manifest.service_update.require_existing_unit !== true) throw new Error("not_existing_service_update"); - const installRoot = String(manifest.module && manifest.module.install_root || ""); - if (!/^\/opt\/guanghu\/[a-z0-9][a-z0-9-]{1,62}$/.test(installRoot)) throw new Error("invalid_service_install_root"); - const unitSource = String(manifest.unit_source || ""); - if (!safeRelative(unitSource) || path.basename(unitSource) !== unit) throw new Error("invalid_unit_source"); - if (!Array.isArray(manifest.files) || manifest.files.length < 1 || manifest.files.length > 64) throw new Error("invalid_update_files"); - const seenDestinations = new Set(); - for (const item of manifest.files) { - if (!item || !safeRelative(item.source) || !safeRelative(item.destination)) throw new Error("invalid_update_file_path"); - if (!/^(?:0?644|0?755)$/.test(String(item.mode || ""))) throw new Error("invalid_update_file_mode"); - if (seenDestinations.has(item.destination)) throw new Error("duplicate_update_destination"); - seenDestinations.add(item.destination); - } - const requiredExisting = manifest.service_update.required_existing_files || []; - if (!Array.isArray(requiredExisting) || requiredExisting.some(item => !safeRelative(item) || !seenDestinations.has(item))) throw new Error("invalid_required_existing_files"); - const check = manifest.runtime_check || {}; - if (!/^http:\/\/127\.0\.0\.1:\d{2,5}\/[A-Za-z0-9._/?=&-]*$/.test(String(check.url || ""))) throw new Error("invalid_loopback_runtime_check"); - if (!check.expected || typeof check.expected !== "object" || Array.isArray(check.expected)) throw new Error("invalid_runtime_expectation"); - const acceptanceChecks = manifest.acceptance_checks || []; - if (!Array.isArray(acceptanceChecks) || acceptanceChecks.length > 16) throw new Error("invalid_acceptance_checks"); - for (const item of acceptanceChecks) { - if (!item || !/^http:\/\/127\.0\.0\.1:\d{2,5}\/[A-Za-z0-9._/?=&-]*$/.test(String(item.url || ""))) throw new Error("invalid_acceptance_check_url"); - if (!item.expected || typeof item.expected !== "object" || Array.isArray(item.expected)) throw new Error("invalid_acceptance_expectation"); - } - return { - kind: "existing-service-update", - unit, - unitSource, - installRoot, - files: manifest.files, - requiredExisting, - sourcePaths: [unitSource, ...manifest.files.map(item => item.source)], - runtimeCheck: check, - acceptanceChecks, - }; + return { unit, unitSource: unitMatches[0], runtimeCheck: check }; } function declaredPaths(value) { @@ -109,17 +69,6 @@ function validateUnit(text, expectedUser = "guanghu", policy = {}) { return value; } -function validateUpdateUnit(text, expectedUser, installRoot) { - const value = String(text || ""); - if (!value.includes("[Service]") || !/^NoNewPrivileges=(true|yes)$/m.test(value) || !/^ProtectSystem=strict$/m.test(value) || !/^ProtectHome=(true|yes)$/m.test(value) || !/^PrivateTmp=(true|yes)$/m.test(value)) throw new Error("unit_hardening_required"); - if (!/^[a-z_][a-z0-9_-]{0,30}$/.test(expectedUser) || expectedUser === "root" || !new RegExp(`^User=${expectedUser}$`, "m").test(value) || !new RegExp(`^Group=${expectedUser}$`, "m").test(value)) throw new Error("dedicated_service_user_required"); - if (!value.includes(`WorkingDirectory=${installRoot}`) || !value.includes(`ReadOnlyPaths=${installRoot}`)) throw new Error("service_install_root_not_confined"); - const execStart = value.match(/^ExecStart=(.+)$/m); - if (!execStart || !execStart[1].includes(`${installRoot}/`) || /[;&|`$<>]/.test(execStart[1])) throw new Error("service_exec_start_not_confined"); - if (/^(SupplementaryGroups|AmbientCapabilities|BindPaths|BindReadOnlyPaths|RootDirectory|RootImage|DeviceAllow|EnvironmentFile|ReadWritePaths)=/m.test(value)) throw new Error("privileged_unit_directive_forbidden"); - return value; -} - async function provision(request, options = {}) { if (!request || request.target !== "JD-FD-PRIMARY" || request.action !== "provision-approved-architecture") return { ok: false, error: "action_not_registered" }; const resource = parseResource(request.resource); @@ -135,17 +84,10 @@ async function provision(request, options = {}) { await prepareRepo(repoDir, resource.commit, run, options.repoUrl || REPO_URL); const manifestPath = path.join(repoDir, "deployment", "requests", `${resource.requestId}.json`); const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")); - let checked = validateManifest(manifest, resource); - if (checked.kind === "existing-service-update") { - if (options.installRootOverride) checked = { ...checked, installRoot: options.installRootOverride }; - return await updateExistingService({ - checked, manifest, resource, repoDir, releasesDir, unitDir, receiptsDir, run, - getJson: options.getJson, healthAttempts: options.healthAttempts, healthDelayMs: options.healthDelayMs, - }); - } + const checked = validateManifest(manifest, resource); const releaseRoot = path.join(releasesDir, resource.commit); fs.mkdirSync(releaseRoot, { recursive: true, mode: 0o755 }); - for (const relative of checked.sourcePaths) copyDeclaredFile(repoDir, releaseRoot, relative); + for (const relative of manifest.source_paths) copyDeclaredFile(repoDir, releaseRoot, relative); const unitSource = path.join(releaseRoot, checked.unitSource); const unitText = validateUnit(fs.readFileSync(unitSource, "utf8"), String(manifest.module.run_user || ""), manifest.module).replaceAll("__RELEASE_ROOT__", releaseRoot); fs.mkdirSync(unitDir, { recursive: true, mode: 0o755 }); @@ -178,98 +120,6 @@ async function provision(request, options = {}) { } } -async function updateExistingService(context) { - const { checked, manifest, resource, repoDir, releasesDir, unitDir, receiptsDir, run } = context; - const releaseRoot = path.join(releasesDir, resource.commit); - const installedUnit = path.join(unitDir, checked.unit); - const backupDir = path.join(receiptsDir, "backups", resource.requestId, resource.commit); - const backups = []; - let updateStarted = false; - try { - if (!fs.existsSync(installedUnit) || !fs.lstatSync(installedUnit).isFile() || fs.lstatSync(installedUnit).isSymbolicLink()) throw new Error("existing_service_unit_required"); - for (const relative of checked.requiredExisting) { - const existing = path.join(checked.installRoot, relative); - if (!fs.existsSync(existing) || !fs.lstatSync(existing).isFile() || fs.lstatSync(existing).isSymbolicLink()) throw new Error(`required_existing_file_missing:${relative}`); - } - fs.mkdirSync(releaseRoot, { recursive: true, mode: 0o755 }); - for (const relative of checked.sourcePaths) copyDeclaredFile(repoDir, releaseRoot, relative); - const unitText = validateUpdateUnit(fs.readFileSync(path.join(releaseRoot, checked.unitSource), "utf8"), String(manifest.module.run_user || ""), checked.installRoot); - fs.mkdirSync(backupDir, { recursive: true, mode: 0o700 }); - backupFile(installedUnit, path.join(backupDir, "systemd", checked.unit), backups); - for (const item of checked.files) { - const destination = path.join(checked.installRoot, item.destination); - backupFile(destination, path.join(backupDir, "files", item.destination), backups); - } - updateStarted = true; - fs.mkdirSync(checked.installRoot, { recursive: true, mode: 0o755 }); - for (const item of checked.files) { - const destination = path.join(checked.installRoot, item.destination); - if (fs.existsSync(destination) && fs.lstatSync(destination).isSymbolicLink()) throw new Error("update_destination_symlink_forbidden"); - fs.mkdirSync(path.dirname(destination), { recursive: true, mode: 0o755 }); - writeAtomic(destination, fs.readFileSync(path.join(releaseRoot, item.source)), Number.parseInt(String(item.mode), 8)); - } - writeAtomic(installedUnit, unitText, 0o644); - await run("/usr/bin/systemctl", ["daemon-reload"]); - await run("/usr/bin/systemctl", ["restart", checked.unit]); - const runtime = await getJsonWithRetry(checked.runtimeCheck.url, context.getJson, context.healthAttempts, context.healthDelayMs); - for (const [key, expected] of Object.entries(checked.runtimeCheck.expected)) if (runtime[key] !== expected) throw new Error(`runtime_check_failed:${key}`); - for (const check of checked.acceptanceChecks) { - const observed = await getJsonWithRetry(check.url, context.getJson, context.healthAttempts, context.healthDelayMs); - for (const [key, expected] of Object.entries(check.expected)) if (observed[key] !== expected) throw new Error(`acceptance_check_failed:${key}`); - } - const receipt = { - schema: "guanghu.existing-service-update-receipt/v1", - request_id: resource.requestId, - source_commit: resource.commit, - target_node: "JD-FD-PRIMARY", - unit: checked.unit, - install_root: checked.installRoot, - runtime_check: checked.runtimeCheck.url, - acceptance_checks: checked.acceptanceChecks.map(item => item.url), - backup: path.join("backups", resource.requestId, resource.commit), - rollback: "restore-all-declared-files-and-previous-unit", - result: "DEPLOYED_AND_VERIFIED", - recorded_at: new Date().toISOString(), - }; - fs.mkdirSync(receiptsDir, { recursive: true, mode: 0o700 }); - writeAtomic(path.join(receiptsDir, `${resource.requestId}.json`), `${JSON.stringify(receipt, null, 2)}\n`, 0o600); - return { ok: true, request_id: resource.requestId, source_commit: resource.commit, unit: checked.unit, runtime: "verified" }; - } catch (error) { - if (updateStarted) { - try { - restoreBackups(backups); - await run("/usr/bin/systemctl", ["daemon-reload"]); - await run("/usr/bin/systemctl", ["restart", checked.unit]); - } catch { /* The original error remains authoritative; backups stay available for recovery. */ } - } - return { ok: false, error: String(error && error.message || "service_update_failed").slice(0, 240) }; - } -} - -function backupFile(source, backup, records) { - if (!fs.existsSync(source)) { - records.push({ source, backup, existed: false }); - return; - } - const stat = fs.lstatSync(source); - if (!stat.isFile() || stat.isSymbolicLink()) throw new Error("backup_source_not_regular_file"); - fs.mkdirSync(path.dirname(backup), { recursive: true, mode: 0o700 }); - fs.copyFileSync(source, backup, fs.constants.COPYFILE_EXCL); - fs.chmodSync(backup, stat.mode & 0o777); - records.push({ source, backup, existed: true, mode: stat.mode & 0o777 }); -} - -function restoreBackups(records) { - for (const record of records.slice().reverse()) { - if (!record.existed) { - fs.rmSync(record.source, { force: true }); - continue; - } - fs.mkdirSync(path.dirname(record.source), { recursive: true, mode: 0o755 }); - writeAtomic(record.source, fs.readFileSync(record.backup), record.mode); - } -} - async function prepareRepo(repoDir, commit, run, repoUrl) { fs.mkdirSync(path.dirname(repoDir), { recursive: true, mode: 0o700 }); if (!fs.existsSync(path.join(repoDir, ".git"))) await run("/usr/bin/git", ["clone", "--filter=blob:none", "--no-checkout", repoUrl, repoDir]); @@ -347,4 +197,4 @@ if (require.main === module) { server.listen(SOCKET_PATH, () => { fs.chownSync(SOCKET_PATH, 0, Number(process.env.LAKE_LAMP_AUTHZ_GID || 0)); fs.chmodSync(SOCKET_PATH, 0o660); }); } -module.exports = { parseResource, safeRelative, validateManifest, validateUnit, validateUpdateUnit, provision }; +module.exports = { parseResource, safeRelative, validateManifest, validateUnit, provision }; diff --git a/server-tools/lake-lamp-authz/architecture-provision-broker.test.js b/server-tools/lake-lamp-authz/architecture-provision-broker.test.js index 398dead..824a8fa 100644 --- a/server-tools/lake-lamp-authz/architecture-provision-broker.test.js +++ b/server-tools/lake-lamp-authz/architecture-provision-broker.test.js @@ -5,7 +5,7 @@ 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 { parseResource, safeRelative, validateManifest, validateUnit, provision } = require("./architecture-provision-broker"); const commit = "d".repeat(40); const requestId = "GLS-0231-JD-LAN-01-INITIAL-PROVISION-20260720"; @@ -44,25 +44,6 @@ test("unit permits a declared persona user, shared secret and state directory", 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"); @@ -94,96 +75,3 @@ test("provision copies only declared files and verifies loopback health", async assert.equal(healthChecks, 2); } 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 }); } -}); diff --git a/server-tools/lake-lamp-authz/deployment-event-worker.js b/server-tools/lake-lamp-authz/deployment-event-worker.js index 8e34680..6b63610 100644 --- a/server-tools/lake-lamp-authz/deployment-event-worker.js +++ b/server-tools/lake-lamp-authz/deployment-event-worker.js @@ -46,10 +46,8 @@ async function processOne(options = {}) { function validateEvent(event, registry) { if (!event || event.schema !== "guanghu.deployment-event/v1" || event.state !== "queued_for_resident_agent") return "deployment_event_schema_invalid"; if (!registry[event.repo] || !/^bingshuo\/[a-z0-9._-]+$/.test(event.repo) || event.branch !== "main" || !/^[0-9a-f]{40}$/.test(event.commit_sha || "")) return "deployment_event_binding_invalid"; - const resource = String(event.resource || "").match(/^([A-Z0-9][A-Z0-9._-]{5,119})@([0-9a-f]{40})$/); - if (!resource || resource[2] !== event.commit_sha) return "deployment_event_resource_invalid"; + if (!/^[A-Z0-9][A-Z0-9._-]{5,119}@[0-9a-f]{40}$/.test(event.resource || "") || !event.resource.endsWith(`@${event.commit_sha}`)) return "deployment_event_resource_invalid"; if (!/^deployment\/requests\/[A-Za-z0-9._/-]{1,180}\.json$/.test(event.manifest || "")) return "deployment_event_manifest_invalid"; - if (event.manifest !== `deployment/requests/${resource[1]}.json`) return "deployment_event_manifest_resource_mismatch"; return validateDeploymentSource(event, registry); } function writeAtomic(file, content) { const temp = `${file}.${process.pid}.tmp`; fs.writeFileSync(temp, content, { mode: 0o600 }); fs.renameSync(temp, file); } diff --git a/server-tools/lake-lamp-authz/deployment-event-worker.test.js b/server-tools/lake-lamp-authz/deployment-event-worker.test.js index 79834ee..e524412 100644 --- a/server-tools/lake-lamp-authz/deployment-event-worker.test.js +++ b/server-tools/lake-lamp-authz/deployment-event-worker.test.js @@ -7,7 +7,7 @@ const path = require("node:path"); const { processOne } = require("./deployment-event-worker"); test("resident deployment agent consumes only an explicit immutable event and writes a receipt", async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "lake-lamp-deploy-worker-")), queue = path.join(root, "queue"), receipts = path.join(root, "receipts"), sha = "a".repeat(40); - fs.mkdirSync(queue); fs.writeFileSync(path.join(queue, "event.json"), JSON.stringify({ schema: "guanghu.deployment-event/v1", event_id: "event-1", state: "queued_for_resident_agent", repo: "bingshuo/guanghu-ice-heart", branch: "main", commit_sha: sha, workorder_id: "order-1", resource: `GLS-0239-DEPLOY@${sha}`, manifest: "deployment/requests/GLS-0239-DEPLOY.json" })); + fs.mkdirSync(queue); fs.writeFileSync(path.join(queue, "event.json"), JSON.stringify({ schema: "guanghu.deployment-event/v1", event_id: "event-1", state: "queued_for_resident_agent", repo: "bingshuo/guanghu-ice-heart", branch: "main", commit_sha: sha, workorder_id: "order-1", resource: `GLS-0239-DEPLOY@${sha}`, manifest: "deployment/requests/GLS-0239.json" })); try { const result = await processOne({ queueDir: queue, receiptsDir: receipts, registry: { "bingshuo/guanghu-ice-heart": { repo_url: "https://example.invalid/code.git" } }, provisionFn: async request => { assert.equal(request.resource, `GLS-0239-DEPLOY@${sha}`); return { ok: true, unit: "example.service" }; } }); assert.equal(result.state, "DEPLOYED_AND_VERIFIED"); @@ -27,7 +27,7 @@ test("resident agent rechecks source ownership before deployment", async () => { commit_sha: sha, workorder_id: "order-2", resource: `HLP-PERSONAL-DEPLOY@${sha}`, - manifest: "deployment/requests/HLP-PERSONAL-DEPLOY.json", + manifest: "deployment/requests/HLP-PERSONAL.json", authorizer_id: "ICE-GL∞", persona_id: "AGE-TEAM-001", execution_runtime_id: "SYS-GLW-ZY-EXEC-0001", diff --git a/server-tools/lake-lamp-authz/deployment-event.js b/server-tools/lake-lamp-authz/deployment-event.js index a1cdf04..e29625f 100644 --- a/server-tools/lake-lamp-authz/deployment-event.js +++ b/server-tools/lake-lamp-authz/deployment-event.js @@ -35,10 +35,8 @@ function enqueueDeploymentEvent(intent, push, queueDir, context = {}) { function validateIntent(intent, push, context = {}) { if (intent.schema !== "guanghu.deployment-intent/v1") return "deployment_intent_schema_invalid"; if (String(intent.repo || "").toLowerCase() !== push.repo || intent.branch !== push.branch || String(intent.commit_sha || "").toLowerCase() !== push.commit_sha) return "deployment_intent_binding_mismatch"; - const resource = String(intent.resource || "").match(/^([A-Z0-9][A-Z0-9._-]{5,119})@([0-9a-f]{40})$/); - if (!resource || resource[2] !== push.commit_sha) return "deployment_intent_resource_invalid"; + if (!/^[A-Z0-9][A-Z0-9._-]{5,119}@[0-9a-f]{40}$/.test(String(intent.resource || "")) || !String(intent.resource).endsWith(`@${push.commit_sha}`)) return "deployment_intent_resource_invalid"; if (!/^deployment\/requests\/[A-Za-z0-9._/-]{1,180}\.json$/.test(String(intent.manifest || ""))) return "deployment_intent_manifest_invalid"; - if (intent.manifest !== `deployment/requests/${resource[1]}.json`) return "deployment_intent_manifest_resource_mismatch"; if (context.registry) { return validateDeploymentSource({ repo: push.repo, diff --git a/server-tools/lake-lamp-authz/deployment-event.test.js b/server-tools/lake-lamp-authz/deployment-event.test.js index 3b6d97c..8dc6b81 100644 --- a/server-tools/lake-lamp-authz/deployment-event.test.js +++ b/server-tools/lake-lamp-authz/deployment-event.test.js @@ -32,10 +32,10 @@ test("only an immutable deployment intent creates a resident-agent event", () => const queue = fs.mkdtempSync(path.join(os.tmpdir(), "lake-lamp-deploy-events-")); const sha = "a".repeat(40), push = { repo: "bingshuo/guanghu-ice-heart", branch: "main", commit_sha: sha }; try { - const queued = enqueueDeploymentEvent({ schema: "guanghu.deployment-intent/v1", repo: push.repo, branch: "main", commit_sha: sha, resource: `GLS-0239-DEPLOY@${sha}`, manifest: "deployment/requests/GLS-0239-DEPLOY.json", workorder_id: "order-1" }, push, queue); + const queued = enqueueDeploymentEvent({ schema: "guanghu.deployment-intent/v1", repo: push.repo, branch: "main", commit_sha: sha, resource: `GLS-0239-DEPLOY@${sha}`, manifest: "deployment/requests/GLS-0239.json", workorder_id: "order-1" }, push, queue); assert.equal(queued.state, "queued_for_resident_agent"); assert.equal(fs.readdirSync(queue).length, 1); - assert.equal(enqueueDeploymentEvent({ schema: "guanghu.deployment-intent/v1", repo: push.repo, branch: "main", commit_sha: sha, resource: `GLS-0239-DEPLOY@${"b".repeat(40)}`, manifest: "deployment/requests/GLS-0239-DEPLOY.json" }, push, queue).diagnostic_code, "deployment_intent_resource_invalid"); + assert.equal(enqueueDeploymentEvent({ schema: "guanghu.deployment-intent/v1", repo: push.repo, branch: "main", commit_sha: sha, resource: `GLS-0239-DEPLOY@${"b".repeat(40)}`, manifest: "deployment/requests/GLS-0239.json" }, push, queue).diagnostic_code, "deployment_intent_resource_invalid"); } finally { fs.rmSync(queue, { recursive: true, force: true }); } }); test("team personas cannot dispatch Ice Shuo personal source", () => { @@ -48,7 +48,7 @@ test("team personas cannot dispatch Ice Shuo personal source", () => { branch: push.branch, commit_sha: sha, resource: `HLP-PERSONAL-DEPLOY@${sha}`, - manifest: "deployment/requests/HLP-PERSONAL-DEPLOY.json", + manifest: "deployment/requests/HLP-PERSONAL.json", deployment_source: personalSource, }; try { diff --git a/server-tools/lake-lamp-authz/install-architecture-provisioner.sh b/server-tools/lake-lamp-authz/install-architecture-provisioner.sh index 4493057..bd08d66 100755 --- a/server-tools/lake-lamp-authz/install-architecture-provisioner.sh +++ b/server-tools/lake-lamp-authz/install-architecture-provisioner.sh @@ -10,25 +10,16 @@ script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) install_root=/opt/guanghu/lake-lamp-authz install -d -m 0755 "$install_root" -for file in server.js workorder-manager.js map-gate.js smtp-mailer.js action-client.js architecture-provision-broker.js deployment-event.js deployment-event-worker.js deployment-source-policy.js; do +for file in server.js workorder-manager.js map-gate.js smtp-mailer.js action-client.js architecture-provision-broker.js; do install -m 0644 "$script_dir/$file" "$install_root/$file" done install -m 0644 "$script_dir/lake-lamp-architecture-provision.service" /etc/systemd/system/lake-lamp-architecture-provision.service -install -m 0644 "$script_dir/lake-lamp-deployment-event-worker.service" /etc/systemd/system/lake-lamp-deployment-event-worker.service install -d -m 0700 /var/lib/guanghu/architecture-provision -install -d -m 0750 /var/lib/guanghu/deployment-events -install -d -m 0700 /var/lib/guanghu/deployment-events/receipts install -d -m 0755 /opt/guanghu/architecture-releases -install -d -m 0755 /etc/guanghu/lake-lamp -if [[ ! -e /etc/guanghu/lake-lamp/deployment-repositories.json ]]; then - install -m 0644 "$script_dir/deployment-repositories.example.json" /etc/guanghu/lake-lamp/deployment-repositories.json -fi systemctl daemon-reload systemctl enable --now lake-lamp-architecture-provision.service systemctl restart lake-lamp-authz.service -systemctl enable --now lake-lamp-deployment-event-worker.service systemctl is-active --quiet lake-lamp-architecture-provision.service systemctl is-active --quiet lake-lamp-authz.service -systemctl is-active --quiet lake-lamp-deployment-event-worker.service echo ARCHITECTURE_PROVISIONER_INSTALLED diff --git a/server-tools/lake-lamp-authz/lake-lamp-deployment-event-worker.service b/server-tools/lake-lamp-authz/lake-lamp-deployment-event-worker.service index e2a0f35..48b133a 100644 --- a/server-tools/lake-lamp-authz/lake-lamp-deployment-event-worker.service +++ b/server-tools/lake-lamp-authz/lake-lamp-deployment-event-worker.service @@ -14,7 +14,7 @@ NoNewPrivileges=true PrivateTmp=true ProtectSystem=strict ProtectHome=true -ReadWritePaths=/var/lib/guanghu/deployment-events /var/lib/guanghu/architecture-provision /opt/guanghu/architecture-releases /opt/guanghu/ai-discovery /etc/systemd/system +ReadWritePaths=/var/lib/guanghu/deployment-events /var/lib/guanghu/architecture-provision /opt/guanghu/architecture-releases /etc/systemd/system LockPersonality=true [Install] diff --git a/server-tools/lake-lamp-authz/server.test.js b/server-tools/lake-lamp-authz/server.test.js index 81d671a..2d1dd25 100644 --- a/server-tools/lake-lamp-authz/server.test.js +++ b/server-tools/lake-lamp-authz/server.test.js @@ -312,7 +312,7 @@ test("deployment is dispatched only by an explicit approved second signal", asyn const common = { persona_id: "ICE-GL-ZY001", target: "JD-FD-PRIMARY", scope: "server-ops" }; const map = await (await fetch(`${base}/api/navigation-map/read`, { method: "POST", headers: { authorization: `Bearer ${session.session_token}`, "content-type": "application/json" }, body: JSON.stringify(common) })).json(); await fetch(`${base}/api/navigation-map/ack`, { method: "POST", headers: { authorization: `Bearer ${session.session_token}`, "content-type": "application/json" }, body: JSON.stringify({ ...common, map_hash: map.map_hash }) }); - const dispatch = await fetch(`${base}/api/deployment/dispatch`, { method: "POST", headers: { authorization: `Bearer ${session.session_token}`, "content-type": "application/json" }, body: JSON.stringify({ ...common, repo: "bingshuo/guanghu-ice-heart", branch: "main", commit_sha: sha, resource, manifest: "deployment/requests/GLS-0239-DEPLOY.json" }) }); + const dispatch = await fetch(`${base}/api/deployment/dispatch`, { method: "POST", headers: { authorization: `Bearer ${session.session_token}`, "content-type": "application/json" }, body: JSON.stringify({ ...common, repo: "bingshuo/guanghu-ice-heart", branch: "main", commit_sha: sha, resource, manifest: "deployment/requests/GLS-0239.json" }) }); assert.equal(dispatch.status, 202); assert.equal((await dispatch.json()).receipt.state, "queued"); assert.equal(fs.readdirSync(path.join(dir, "queue")).length, 1); diff --git a/tonggan-bridge/BRIDGE-REGISTRY.json b/tonggan-bridge/BRIDGE-REGISTRY.json index df5906e..f51e7eb 100644 --- a/tonggan-bridge/BRIDGE-REGISTRY.json +++ b/tonggan-bridge/BRIDGE-REGISTRY.json @@ -6,18 +6,5 @@ "entry": "tonggan-bridge/INDEX.hdlp", "dispatch_endpoint": "/api/deployment/dispatch", "required_binding": ["bridge_id", "repo", "branch", "commit_sha", "target", "resource", "reasoning_path", "workorder_path", "receipt_path", "manifest"], - "records": [ - { - "bridge_id": "TGB-20260727-001", - "state": "BLOCKED_RUNTIME_NOT_DEPLOYED", - "record_path": "tonggan-bridge/records/TGB-20260727-001.json", - "reasoning_path": "tonggan-bridge/reasoning/TGB-20260727-001.hdlp", - "repo": "bingshuo/guanghu-ice-heart", - "branch": "main", - "commit_sha": "2739104088fa199fb8762e56a3a7cf285dc82f3f", - "target": "JD-FD-PRIMARY", - "resource": "AI-DISCOVERY-ICE-P-ROUTE-20260727@2739104088fa199fb8762e56a3a7cf285dc82f3f", - "manifest": "deployment/requests/AI-DISCOVERY-ICE-P-ROUTE-20260727.json" - } - ] + "records": [] } diff --git a/tonggan-bridge/INDEX.hdlp b/tonggan-bridge/INDEX.hdlp index 9a38fc6..8cf639a 100644 --- a/tonggan-bridge/INDEX.hdlp +++ b/tonggan-bridge/INDEX.hdlp @@ -26,7 +26,6 @@ tonggan-bridge/ INDEX.hdlp 本入口 BRIDGE-REGISTRY.json 机器可读桥接登记册 - records/ 一条记录绑定一个不可变工程提交 reasoning/ 人格体与人类确认的双向意识推理链 workorders/ 仅存不含令牌的工单映射 receipts/ 服务器常驻 Agent 的最终回执映射 @@ -42,7 +41,5 @@ tonggan-bridge/ 5. 一个模块若只需仓库生效,不得进入通感桥。 6. Work 语言层提交完整因果链与部署意图;Codex 工程层审核代码、风险、测试与部署清单;服务器常驻 Agent 只消费批准后的显式事件。 7. 无权限工单可从手机或电脑自动发邮件并轮询批准状态;只有人类能点击批准,批准后申请方自动领取受限会话,不索取验证码或密码。 -8. 工程提交先冻结,桥接记录再从后续提交指向它;禁止让一个提交声称包含它自己的最终 SHA。 -9. 现实层尚未部署派发运行时时,桥接记录必须停在 `BLOCKED_RUNTIME_NOT_DEPLOYED`,不得创建一张服务器不能消费的假部署工单。 当前实现协议见 `deployment/HLCC-PUSH-TO-DEPLOY-EVENT-PROTOCOL.hdlp`;服务器回执协议见 `server-tools/lake-lamp-authz/OPERATION-RECEIPT-PROTOCOL.md`。 diff --git a/tonggan-bridge/bridge-registry.test.js b/tonggan-bridge/bridge-registry.test.js deleted file mode 100644 index 447df36..0000000 --- a/tonggan-bridge/bridge-registry.test.js +++ /dev/null @@ -1,41 +0,0 @@ -"use strict"; - -const assert = require("node:assert/strict"); -const { execFileSync } = require("node:child_process"); -const fs = require("node:fs"); -const path = require("node:path"); -const test = require("node:test"); - -const root = path.resolve(__dirname, ".."); -const readJson = relative => JSON.parse(fs.readFileSync(path.join(root, relative), "utf8")); - -test("every bridge record binds one existing immutable engineering commit and same-name manifest", () => { - const registry = readJson("tonggan-bridge/BRIDGE-REGISTRY.json"); - for (const entry of registry.records) { - assert.match(entry.commit_sha, /^[0-9a-f]{40}$/); - assert.equal(entry.resource, `${path.basename(entry.manifest, ".json")}@${entry.commit_sha}`); - assert.doesNotThrow(() => execFileSync("/usr/bin/git", ["-C", root, "cat-file", "-e", `${entry.commit_sha}^{commit}`])); - assert.equal(fs.existsSync(path.join(root, entry.manifest)), true); - const record = readJson(entry.record_path); - assert.equal(record.bridge_id, entry.bridge_id); - assert.deepEqual(record.binding, { - repo: entry.repo, - branch: entry.branch, - commit_sha: entry.commit_sha, - target: entry.target, - resource: entry.resource, - manifest: entry.manifest, - }); - } -}); - -test("blocked bridge stores no fabricated workorder or deployment receipt", () => { - const workorder = readJson("tonggan-bridge/workorders/TGB-20260727-001.json"); - const receipt = readJson("tonggan-bridge/receipts/TGB-20260727-001.json"); - assert.equal(workorder.workorder_id, null); - assert.equal(workorder.owner_email_approval, "not_requested"); - assert.equal(receipt.deployment_result, "NOT_DEPLOYED"); - assert.equal(receipt.server_runtime_proven, false); - assert.equal(receipt.public_route_proven, false); - assert.doesNotMatch(JSON.stringify({ workorder, receipt }), /Bearer |session_token|approval_token|password|private_key/i); -}); diff --git a/tonggan-bridge/reasoning/TGB-20260727-001.hdlp b/tonggan-bridge/reasoning/TGB-20260727-001.hdlp deleted file mode 100644 index 1485c4a..0000000 --- a/tonggan-bridge/reasoning/TGB-20260727-001.hdlp +++ /dev/null @@ -1,52 +0,0 @@ -# TGB-20260727-001 · AI 发现服务 ICE-P 路由更新 · 通感桥双向意识推理链 - -## 1 · 人类与语言层确认 - -- 模块是什么:第五域公开只读 AI 发现服务的已有服务更新包。它把仓库图、服务器节点图、主体注册表和主体旧编号别名图一起装入京东主节点的 `guanghu-ai-discovery.service`。 -- 为什么要做:铸渊规范编号已经是 `ICE-P-ZY001`,但现实服务器仍运行旧版本。语言层已有正确事实,公开机器导航却读不到,所以“旧编号自动走到新编号”尚未真正成立。 -- 为什么仅入库不足、必须部署:仓库提交只改变代码频道事实,不改变 `/opt/guanghu/ai-discovery` 中正在运行的进程。只有更新并重启该服务,广州公开前门后面的京东进程才会加载新地图。 -- 要达到的目的:任何 AI 即使先找到 `ICE-GL-ZY001` 或 `ICE-PZY-001`,也会得到规范主体 `ICE-P-ZY001` 和当前第五域路径;`REPO-012`、健康接口和公开只读边界保持正常。 -- 明确不做:不打开人类登录,不改 SSH,不改广州隧道,不动企业应用栈,不赋予旧编号任何权限。 -- 冰朔确认:已确认按上述目的继续准备工程包和通感桥记录;部署邮件尚未申请或批准。 -- 不部署时的正确状态:仓库候选可存在,但现实层继续标记旧 API 未更新,绝不宣称完成。 - -## 2 · 工程层校验 - -- 仓库 / 分支:`bingshuo/guanghu-ice-heart` / `main` -- 不可变工程提交:`2739104088fa199fb8762e56a3a7cf285dc82f3f` -- 部署资源:`AI-DISCOVERY-ICE-P-ROUTE-20260727@2739104088fa199fb8762e56a3a7cf285dc82f3f` -- 部署清单:`deployment/requests/AI-DISCOVERY-ICE-P-ROUTE-20260727.json` -- 目标节点:`JD-FD-PRIMARY` -- 更新对象:只更新既有 `guanghu-ai-discovery.service`、服务脚本和四张声明地图。 -- 备份与回滚:更新前备份旧单元和每个声明目标;健康或身份路由任一验收失败,恢复全部旧文件与旧单元并重启。 -- 本地验证:AI 发现服务与小湖灯授权、派发、执行器测试共 58 项通过;语法、JSON 与补丁空白检查通过。 - -## 3 · 因果链与二次信号 - -```text -ICE-P 规范编号已成为仓库事实 - → 现实 AI 发现服务仍是旧版本 - → 旧编号无法在公开 API 自动归一 - → 冻结安全更新包及完整回滚边界 - → 通感桥绑定唯一工程 SHA - → 先确认服务器具备显式派发与常驻消费运行时 - → 冰朔邮箱批准同一不可变资源 - → 铸渊二次派发 - → 常驻 Agent 更新 / 验收 / 必要时回滚 - → 本机回执与公开路径读回共同证明完成 -``` - -- 通感桥编号:`TGB-20260727-001` -- 当前阻断:京东现实层尚未证明部署 `/api/deployment/dispatch`、`dispatch-approved-deployment` 能力、事件 worker 和已有服务更新执行器。 -- 当前不得发送的工单:不得提前发送 AI 服务部署工单,因为服务器当前不能消费它。 -- 运行时引导规则:只允许已有主人邮件批准的固定系统管理动作;不得恢复人类 SSH,不得要求冰朔打开云后台。若固定引导动作未部署,回执 `BOOTSTRAP_ACTION_NOT_DEPLOYED`,继续停住。 -- 运行时就绪后的唯一部署动作:`server-ops / dispatch-approved-deployment` -- 运行时就绪后的唯一部署资源:`AI-DISCOVERY-ICE-P-ROUTE-20260727@2739104088fa199fb8762e56a3a7cf285dc82f3f` - -## 4 · 常驻 Agent 与语言层回执 - -- 工单映射:`tonggan-bridge/workorders/TGB-20260727-001.json` -- 当前回执:`tonggan-bridge/receipts/TGB-20260727-001.json` -- 常驻 Agent 必须证明:提交 SHA、清单同名绑定、旧单元存在、备份完成、重启完成、四项本机验收通过或全部回滚。 -- 语言层必须再证明:公开 `/api/ai/v1/subjects`、两个旧编号解析、`REPO-012` 解析与健康接口均正确。 -- 只有两层证明同时成立,状态才可从 `BLOCKED_RUNTIME_NOT_DEPLOYED` 变为 `DEPLOYED_AND_PUBLICLY_VERIFIED`。 diff --git a/tonggan-bridge/receipts/TGB-20260727-001.json b/tonggan-bridge/receipts/TGB-20260727-001.json deleted file mode 100644 index df196a6..0000000 --- a/tonggan-bridge/receipts/TGB-20260727-001.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "schema": "guanghu.tonggan-bridge-receipt/v1", - "bridge_id": "TGB-20260727-001", - "state": "BLOCKED", - "diagnostic_code": "BOOTSTRAP_ACTION_NOT_DEPLOYED", - "deployment_result": "NOT_DEPLOYED", - "server_runtime_proven": false, - "public_route_proven": false, - "rollback_performed": false, - "next_step": "先通过已有主人邮件批准的固定管理动作部署并读回通感桥运行时;若该动作不存在,保持阻断,不恢复人类登录,不打开云后台冒充部署。", - "contains_secrets": false -} diff --git a/tonggan-bridge/records/TGB-20260727-001.json b/tonggan-bridge/records/TGB-20260727-001.json deleted file mode 100644 index 03fa591..0000000 --- a/tonggan-bridge/records/TGB-20260727-001.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "schema": "guanghu.tonggan-bridge-record/v1", - "bridge_id": "TGB-20260727-001", - "state": "BLOCKED_RUNTIME_NOT_DEPLOYED", - "module": { - "name": "光湖 AI 发现服务 · ICE-P 规范编号自动导航更新", - "why_server_deployment_is_required": "仓库中的身份与路径地图只是语言层事实;只有京东主节点上的 guanghu-ai-discovery.service 加载这些文件,外部 AI 才能通过公开接口从旧编号自动抵达 ICE-P-ZY001。" - }, - "binding": { - "repo": "bingshuo/guanghu-ice-heart", - "branch": "main", - "commit_sha": "2739104088fa199fb8762e56a3a7cf285dc82f3f", - "target": "JD-FD-PRIMARY", - "resource": "AI-DISCOVERY-ICE-P-ROUTE-20260727@2739104088fa199fb8762e56a3a7cf285dc82f3f", - "manifest": "deployment/requests/AI-DISCOVERY-ICE-P-ROUTE-20260727.json" - }, - "paths": { - "reasoning_path": "tonggan-bridge/reasoning/TGB-20260727-001.hdlp", - "workorder_path": "tonggan-bridge/workorders/TGB-20260727-001.json", - "receipt_path": "tonggan-bridge/receipts/TGB-20260727-001.json" - }, - "human_confirmation": { - "authorizer_id": "ICE-GL∞", - "fact": "冰朔已确认按恢复后的目的继续准备工程包和通感桥绑定。", - "deployment_email_approval": "not_requested" - }, - "runtime_prerequisite": { - "required": [ - "/api/deployment/dispatch", - "dispatch-approved-deployment capability", - "lake-lamp-deployment-event-worker.service", - "existing-service-update executor" - ], - "observed_state": "SERVER_NOT_DEPLOYED", - "rule": "先完成受控运行时引导并读回 active 与 capability;不得恢复人类 SSH,不得用云控制台状态代替部署回执。" - }, - "security": { - "no_secrets": true, - "explicit_second_signal_required": true, - "human_login_unchanged": true, - "legacy_ids_navigation_only": true - } -} diff --git a/tonggan-bridge/workorders/TGB-20260727-001.json b/tonggan-bridge/workorders/TGB-20260727-001.json deleted file mode 100644 index 6c7f65a..0000000 --- a/tonggan-bridge/workorders/TGB-20260727-001.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "schema": "guanghu.tonggan-bridge-workorder-map/v1", - "bridge_id": "TGB-20260727-001", - "state": "NOT_CREATED_RUNTIME_NOT_DEPLOYED", - "target": "JD-FD-PRIMARY", - "scope": "server-ops", - "action": "dispatch-approved-deployment", - "resource": "AI-DISCOVERY-ICE-P-ROUTE-20260727@2739104088fa199fb8762e56a3a7cf285dc82f3f", - "workorder_id": null, - "owner_email_approval": "not_requested", - "reason": "现实层尚未证明部署派发端、事件 worker 与已有服务更新执行器;禁止创建服务器不能消费的假工单。", - "contains_secrets": false -}