diff --git a/server-tools/lake-lamp-authz/authorize-repo-push.js b/server-tools/lake-lamp-authz/authorize-repo-push.js index 3ba58a0..28d0112 100755 --- a/server-tools/lake-lamp-authz/authorize-repo-push.js +++ b/server-tools/lake-lamp-authz/authorize-repo-push.js @@ -51,10 +51,11 @@ async function authorizeRepoPush(options, deps = {}) { const map = await requestJson(fetchImpl, `${baseUrl}/api/navigation-map/read`, common, session.session_token); await requestJson(fetchImpl, `${baseUrl}/api/navigation-map/ack`, { ...common, map_hash: map.map_hash }, session.session_token); const grant = await requestJson(fetchImpl, `${baseUrl}/api/repo-push/grant`, { ...common, repo }, session.session_token); - if (grant.transport && grant.transport.status !== "ready") { - output(`[LL-REPO-PUSH-TRANSPORT-BLOCKED] ${grant.transport.diagnostic_code || "repo_push_transport_unavailable"}`); - output(`NEXT_STEP=${grant.transport.next_step || "读取服务器 operation receipt。"}`); - const error = new Error(grant.transport.diagnostic_code || "repo_push_transport_unavailable"); + if (!grant.transport || grant.transport.status !== "ready") { + const transport = grant.transport || {}; + output(`[LL-REPO-PUSH-TRANSPORT-BLOCKED] ${transport.diagnostic_code || "repo_push_transport_unavailable"}`); + output(`NEXT_STEP=${transport.next_step || "服务器没有返回可验证的受限推送通道;禁止重试裸 git push,读取 operation receipt。"}`); + const error = new Error(transport.diagnostic_code || "repo_push_transport_unavailable"); error.receipt = grant.receipt; throw error; } diff --git a/server-tools/lake-lamp-authz/authorize-repo-push.test.js b/server-tools/lake-lamp-authz/authorize-repo-push.test.js index ddbae9a..e8413b2 100644 --- a/server-tools/lake-lamp-authz/authorize-repo-push.test.js +++ b/server-tools/lake-lamp-authz/authorize-repo-push.test.js @@ -8,6 +8,13 @@ const path = require("node:path"); const { createApp } = require("./server"); const { authorizeRepoPush } = require("./authorize-repo-push"); +function response(status, body) { + return new Response(JSON.stringify(body), { + status, + headers: { "content-type": "application/json" }, + }); +} + test("repo-push helper stops with a server receipt when no safe transport is deployed", async () => { const mail = []; const directory = fs.mkdtempSync(path.join(os.tmpdir(), "lake-lamp-repo-push-")); @@ -56,3 +63,41 @@ test("repo-push helper stops with a server receipt when no safe transport is dep fs.rmSync(directory, { recursive: true, force: true }); } }); + +test("repo-push helper fails closed when an older server omits transport state", async () => { + const calls = []; + const grantPromise = authorizeRepoPush({ + url: "https://example.invalid/authz", + persona: "ICE-GL-ZY001", + repo: "bingshuo/guanghu-ice-heart", + poll: 1, + }, { + sleep: async () => {}, + output: line => calls.push(line), + fetch: async url => { + if (url.endsWith("/api/public/workorders")) { + return response(200, { + request_url: "https://example.invalid/authz/request/opaque", + workorder_id: "workorder", + claim_token: "claim", + expires_in: 60, + }); + } + if (url.endsWith("/claim")) return response(200, { session_token: "session" }); + if (url.endsWith("/api/navigation-map/read")) return response(200, { map_hash: "map" }); + if (url.endsWith("/api/navigation-map/ack")) return response(200, { ok: true }); + if (url.endsWith("/api/repo-push/grant")) { + return response(200, { + ok: true, + repo: "bingshuo/guanghu-ice-heart", + receipt: { diagnostic_code: "repo_push_transport_unavailable" }, + }); + } + throw new Error(`unexpected URL: ${url}`); + }, + }); + + await assert.rejects(grantPromise, /repo_push_transport_unavailable/); + assert.ok(calls.some(line => line.includes("LL-REPO-PUSH-TRANSPORT-BLOCKED"))); + assert.ok(calls.some(line => line.includes("禁止重试裸 git push"))); +});