gitea: tell the controller which files a pull request deletes; say the dependents a merge would build (hq ADR 0238)

The controller asks its planner what a pull request reaches, as if merged: a module whose
manifest the change deletes is one the merge removes, and the plan's dependents are said
on the gate's status beside the modules it moves.
This commit is contained in:
jochen
2026-10-06 22:34:53 +02:00
parent bd7b757dd9
commit dba71a97a8
3 changed files with 14 additions and 6 deletions
+1
View File
@@ -222,6 +222,7 @@ async function pollPulls(client: GiteaClient): Promise<void> {
html_url: pull.html_url,
paths: files.paths,
paths_truncated: files.truncated,
removed: files.removed,
...(moduleDirs ? { module_dirs: moduleDirs, module_dirs_said: true } : {}),
...(mergeCheck !== null ? { merge_check: mergeCheck, merge_check_said: true } : {}),
});
+11 -4
View File
@@ -21,7 +21,10 @@ export const REPO_CHECK_CONTEXT = "mesh/repo-check";
export interface Layer {
verdict: string;
summary: string;
/** The modules a merge of the change would move, as the controller's planner reckons it… */
modules?: string[];
/** …and those it would build after them because they stand on them. */
dependents?: string[];
}
/** What the controller says as `checked` (mesh-controller internal/link, Checked). */
@@ -55,10 +58,10 @@ function stateOf(verdict: string): CommitStatus["state"] {
return verdict === "pass" ? "success" : verdict === "warning" ? "warning" : verdict === "fail" ? "failure" : "error";
}
function described(verdict: string, summary: string, modules?: string[]): string {
function described(verdict: string, summary: string, modules?: string[], dependents?: string[]): string {
// The forge keeps a short description; the rest is the comment's.
let d = `${verdict || "error"}: ${summary}`;
if (modules?.length) d += ` [${modules.join(", ")}]`;
if (modules?.length) d += ` [${modules.join(", ")}${dependents?.length ? ` +${dependents.length} dependent(s)` : ""}]`;
d = d.replace(/\s+/g, " ").trim();
return d.length > 140 ? d.slice(0, 139) + "…" : d;
}
@@ -66,7 +69,10 @@ function described(verdict: string, summary: string, modules?: string[]): string
/** The forge's status for the gate. */
export function statusFor(c: Checked): CommitStatus {
const gate = c.gate ?? { verdict: c.verdict, summary: c.summary };
return { state: stateOf(gate.verdict), context: CHECK_CONTEXT, description: described(gate.verdict, gate.summary, gate.modules) };
return {
state: stateOf(gate.verdict), context: CHECK_CONTEXT,
description: described(gate.verdict, gate.summary, gate.modules, gate.dependents),
};
}
/** Every status a verdict sets: the gate's, and the repository's own check's when it was said. */
@@ -87,7 +93,8 @@ export function commentFor(c: Checked): string | null {
if (gate.verdict === "pass" && !repoWrong) return null;
const lines = [`**Merge check** at \`${c.commit.slice(0, 8)}\``, ""];
lines.push(`- \`${CHECK_CONTEXT}\`: **${(gate.verdict || "error").toUpperCase()}** — ${gate.summary}` +
(gate.modules?.length ? ` (modules: ${gate.modules.join(", ")})` : ""));
(gate.modules?.length ? ` (modules: ${gate.modules.join(", ")}` +
(gate.dependents?.length ? `; built after them: ${gate.dependents.join(", ")}` : "") + ")" : ""));
if (repo) lines.push(`- \`${REPO_CHECK_CONTEXT}\`: **${(repo.verdict || "error").toUpperCase()}** — ${repo.summary}`);
const ran = c.on ? `\n\nRun by the build seat on ${c.on} as \`${c.id}\` (\`builds --log ${c.id}\`).` : "";
const report = c.report ? `\n\n\`\`\`\n${c.report.replace(/```/g, "'''")}\n\`\`\`` : "";
+2 -2
View File
@@ -40,10 +40,10 @@ test("each layer is its own status: the gate with the modules it judged, the rep
const { statusesFor, commentFor, CHECK_CONTEXT, REPO_CHECK_CONTEXT } = await import("../pulls.ts");
const base = { owner: "novox", repo: "mesh-catalog", number: 7, commit: "0123456789abcdef", id: "build-1" };
const both = statusesFor({ ...base, verdict: "pass", summary: "every machine composes",
gate: { verdict: "pass", summary: "every machine composes", modules: ["gitea", "keycloak"] },
gate: { verdict: "pass", summary: "every machine composes", modules: ["gitea", "keycloak"], dependents: ["node-tools"] },
"repo-check": { verdict: "fail", summary: "its merge-check.sh failed: FAIL x" } });
assert.deepEqual(both.map((s) => [s.context, s.state]), [[CHECK_CONTEXT, "success"], [REPO_CHECK_CONTEXT, "failure"]]);
assert.match(both[0].description, /gitea, keycloak/);
assert.match(both[0].description, /gitea, keycloak \+1 dependent/);
assert.match(commentFor({ ...base, verdict: "pass", summary: "", gate: { verdict: "pass", summary: "" },
"repo-check": { verdict: "fail", summary: "its merge-check.sh failed" } }) ?? "", /mesh\/repo-check`: \*\*FAIL/);