Files
mesh-catalog/modules/gitea/protection.ts
T
jschoubben c41c968086 gitea: judge and write only the statuses a base requires, so merges go on mesh/check (issue 1301, S5-S7)
Once main requires mesh/check, nothing writes mesh/merge-gate, and the merge
tool, which judged on it, would refuse every merge. It now judges on the
status the base requires: mesh/check's line names the trunk it judged, an
out-of-date one is refused and nothing is asked (the checker re-checks on a
trunk move, ADR 0447 §4); hq's mesh/repo-check is judged as before. The
announcer, the verdict, a trunk's move and a build's start write the
controller's two statuses only where a base still requires them, so hq keeps
mesh/repo-check and no stale mesh/merge-gate appears anywhere else.
2026-10-22 17:49:53 +02:00

54 lines
2.8 KiB
TypeScript

// A branch's protection (novox/hq ADR 0237): what makes a pull request wait for the mesh's check — `mesh/check`,
// the checker's verdict on the head merged onto its trunk (ADR 0447 §1; hq's `mesh/repo-check` until issue 1296) —
// before it may merge. Pure, so it is tested without a forge; the client does the asking (client.ts).
import { TOOLS_ACCOUNT } from "./tools-account.js";
/** The branch whose rule takes no push and lets only the tools' account merge (novox/hq ADR 0398 §2, issue 948). */
export const MAIN_BRANCH = "main";
/** A branch protection rule, as the forge keeps it — the fields the mesh reads; the forge sends more. */
export interface BranchProtection {
rule_name?: string;
branch_name?: string;
enable_push?: boolean;
enable_status_check?: boolean;
status_check_contexts?: string[] | null;
required_approvals?: number;
block_admin_merge_override?: boolean;
enable_merge_whitelist?: boolean;
merge_whitelist_usernames?: string[] | null;
[field: string]: unknown;
}
/** What a branch's protection is set to. A field not given is left as the rule has it (or the forge's
* default on a new rule), except pushes, which a new rule refuses unless `push` says otherwise. */
export interface ProtectionWanted {
/** The statuses a pull request must have succeeded before it merges, e.g. mesh/check. Empty: none. */
statusChecks: string[];
/** Whether a person may push to the branch directly. */
push?: boolean;
/** Whether an administrator is stopped from merging past a status that has not succeeded. */
blockAdminOverride?: boolean;
/** Who may merge into the branch; for main only ever the tools' account (ADR 0398 §2). Not given: left as it is.
* Empty or null asks for no whitelist, which the guard refuses for main; the body for main ignores it. */
mergeWhitelist?: string[] | null;
}
/** What main's rule always says: no push, and only the tools' account merges (novox/hq ADR 0398 §2, issue 948). */
export function mainRuleBody(): Record<string, unknown> {
return { enable_push: false, enable_merge_whitelist: true, merge_whitelist_usernames: [TOOLS_ACCOUNT] };
}
/** The forge's body for a wanted protection. */
export function protectionBody(want: ProtectionWanted, creating: boolean, branch = ""): Record<string, unknown> {
const checks = [...new Set(want.statusChecks.map((c) => c.trim()).filter(Boolean))];
const body: Record<string, unknown> = { enable_status_check: checks.length > 0, status_check_contexts: checks };
if (want.push !== undefined) body.enable_push = want.push;
else if (creating) body.enable_push = false;
if (want.blockAdminOverride !== undefined) body.block_admin_merge_override = want.blockAdminOverride;
if (branch === MAIN_BRANCH) Object.assign(body, mainRuleBody()); // whatever was asked: main takes no push (ADR 0398 §2)
return body;
}