mesh/merge-gate head 2e32f47e4596 merged into trunk 9492278d934b — pass: builds gitea → novox; no bus step; every machine composes with the change as it di…
mesh/repo-check head 2e32f47e4596 merged into trunk 9492278d934b — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery failed: its walk failed, carrying its own commit: a gate on a first machine (what it carried put back), a build, a machine
The forge answers a repository's protection rules to its administrator alone, and the tools' account no longer is one (ADR 0365), so every delivery building a module failed at the controller's build-source check with a 403. gitea_branch_protection_get now reads the rules with the administrator account (ADR 0375); it only reads, and the protection set and every write stay refused to the tools' account. Issue 772. Co-Authored-By: Claude <noreply@anthropic.com>
60 lines
3.3 KiB
TypeScript
60 lines
3.3 KiB
TypeScript
// The controller's build-source check asks gitea_branch_protection_get for a repository's rules, which the forge
|
|
// answers to a repository's administrator alone. The tools' account is not one (novox/hq ADR 0365 §2), so the read
|
|
// goes with the forge's administrator account, and only the read (ADR 0375, issue 772): a protection edit through the
|
|
// tools still gets the forge's refusal.
|
|
|
|
import { test, after } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { createServer } from "node:http";
|
|
|
|
import { GiteaAdmin, GiteaClient } from "../dist/client.js";
|
|
import { ConfiguredToken } from "../dist/token.js";
|
|
import { getGiteaTools } from "../dist/tools/index.js";
|
|
|
|
const adminBasic = "Basic " + Buffer.from("mesh-admin:secret").toString("base64");
|
|
const rules = [
|
|
{ rule_name: "main", enable_push: false, enable_status_check: true, status_check_contexts: ["mesh/merge-gate"], block_admin_merge_override: true },
|
|
{ branch_name: "release", enable_push: false },
|
|
];
|
|
const seen: string[] = [];
|
|
|
|
const server = createServer((req, res) => {
|
|
const path = (req.url ?? "").replace(/^\/api\/v1/, "");
|
|
const as = req.headers.authorization === adminBasic ? "admin" : "tools";
|
|
seen.push(`${req.method} ${path} as ${as}`);
|
|
const send = (status: number, body: unknown) => { res.writeHead(status, { "Content-Type": "application/json" }); res.end(JSON.stringify(body)); };
|
|
if (path.includes("/branch_protections")) {
|
|
if (as !== "admin") return send(403, { message: "user should be an owner or a collaborator with admin write of a repository" });
|
|
if (req.method === "GET") return send(200, rules);
|
|
}
|
|
send(404, { message: "not found" });
|
|
});
|
|
await new Promise<void>((r) => server.listen(0, "127.0.0.1", () => r()));
|
|
after(() => new Promise<void>((r) => server.close(() => r())));
|
|
const { port } = server.address() as { port: number };
|
|
const url = `http://127.0.0.1:${port}`;
|
|
const tools = getGiteaTools(new GiteaClient(url, new ConfiguredToken("tools-token")), () => new GiteaAdmin(url, "mesh-admin", "secret"));
|
|
const tool = (name: string) => tools.find((t) => t.name === name)!;
|
|
|
|
test("gitea_branch_protection_get answers every rule, read with the administrator account the tools' token cannot read with", async () => {
|
|
const out: any = await tool("gitea_branch_protection_get").run({ owner: "novox", repo: "mesh-controller" });
|
|
assert.deepEqual(out.rules.map((r: any) => r.rule), ["main", "release"]);
|
|
});
|
|
|
|
test("gitea_branch_protection_get answers the rule for a branch, null when it has none", async () => {
|
|
const main: any = await tool("gitea_branch_protection_get").run({ owner: "novox", repo: "mesh-controller", branch: "main" });
|
|
assert.equal(main.branch, "main");
|
|
assert.equal(main.rule.rule, "main");
|
|
assert.equal(main.rule.admin_may_override, false);
|
|
const none: any = await tool("gitea_branch_protection_get").run({ owner: "novox", repo: "mesh-controller", branch: "dev" });
|
|
assert.equal(none.rule, null);
|
|
});
|
|
|
|
test("gitea_branch_protection_set still gets the forge's refusal, as the tools' account", async () => {
|
|
await assert.rejects(
|
|
tool("gitea_branch_protection_set").run({ owner: "novox", repo: "mesh-controller", branch: "main", status_checks: "mesh/merge-gate" }),
|
|
/403/,
|
|
);
|
|
assert.ok(!seen.some((s) => /^(PATCH|POST|PUT|DELETE) .*branch_protections.* as admin$/.test(s)), seen.join("\n"));
|
|
});
|