gitea: post a pull request's change plan with its verdict (hq ADR 0238)
mesh/merge-gate pass: the merge check passed
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: the merge check passed
mesh/delivery superseded: a newer head of the same pull request
The gate's status says what the change does and how it was judged; a change that builds something gets its plan as a comment — the tiers, what each machine receives, and what is not an ordinary send.
This commit is contained in:
+49
-8
@@ -42,6 +42,43 @@ export interface Checked {
|
||||
* controller from before the layers says neither, and its verdict is the gate's. */
|
||||
gate?: Layer;
|
||||
"repo-check"?: Layer;
|
||||
/** The change plan of the commit checked (novox/hq ADR 0238): what a merge of it would build and send. */
|
||||
plan?: ChangePlan;
|
||||
}
|
||||
|
||||
/** A change plan, as the controller says it (mesh-controller internal/link, ChangePlan). */
|
||||
export interface ChangePlan {
|
||||
repository: string;
|
||||
base: string;
|
||||
head: string;
|
||||
moved?: string[];
|
||||
dependents?: string[];
|
||||
new?: string[];
|
||||
unread?: string[];
|
||||
tiers?: string[][];
|
||||
machines?: { machine: string; receives?: string[]; waits?: string[] }[];
|
||||
steps?: string[];
|
||||
summary: string;
|
||||
}
|
||||
|
||||
/** Whether a plan builds anything: a change that touches the mesh's graph. */
|
||||
function builds(plan?: ChangePlan): boolean {
|
||||
return !!plan && ((plan.moved?.length ?? 0) > 0 || (plan.new?.length ?? 0) > 0);
|
||||
}
|
||||
|
||||
/** A change plan as a person reads it on the pull request. */
|
||||
export function planText(plan: ChangePlan): string {
|
||||
const lines = [`**Change plan** — ${plan.summary}`];
|
||||
(plan.tiers ?? []).forEach((tier, i) => lines.push(`- tier ${i}: ${tier.join(", ")}`));
|
||||
for (const m of plan.machines ?? []) {
|
||||
const parts: string[] = [];
|
||||
if (m.receives?.length) parts.push(`receives ${m.receives.join(", ")}`);
|
||||
if (m.waits?.length) parts.push(`waits for a person: ${m.waits.join(", ")}`);
|
||||
lines.push(`- ${m.machine}: ${parts.join("; ")}`);
|
||||
}
|
||||
for (const step of plan.steps ?? []) lines.push(`- ${step}`);
|
||||
if (plan.unread?.length) lines.push(`- read by no module's build: ${plan.unread.join(", ")}`);
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
/** The heads already announced, keyed `owner/repo#number`, so a restart announces nothing twice. */
|
||||
@@ -69,10 +106,12 @@ function described(verdict: string, summary: string, modules?: string[], depende
|
||||
/** 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, gate.dependents),
|
||||
};
|
||||
// With a plan, the status says what the change does and how it was judged: "pass: builds gitea → anchor;
|
||||
// no bus step; every machine composes…".
|
||||
const description = builds(c.plan)
|
||||
? described(gate.verdict, `${c.plan!.summary}; ${gate.summary}`)
|
||||
: described(gate.verdict, gate.summary, gate.modules, gate.dependents);
|
||||
return { state: stateOf(gate.verdict), context: CHECK_CONTEXT, description };
|
||||
}
|
||||
|
||||
/** Every status a verdict sets: the gate's, and the repository's own check's when it was said. */
|
||||
@@ -83,19 +122,21 @@ export function statusesFor(c: Checked): CommitStatus[] {
|
||||
return out;
|
||||
}
|
||||
|
||||
/** The comment a verdict leaves on its pull request, with the check's own account: when the gate is not a
|
||||
* pass, or the repository's own check failed or could not run. A repository with no merge-check.sh is
|
||||
* said by its status alone, not by a comment on every push. */
|
||||
/** The comment a verdict leaves on its pull request, with the check's own account: the change plan of a
|
||||
* change that builds something (novox/hq ADR 0238), and why, when the gate is not a pass or the
|
||||
* repository's own check failed or could not run. A repository with no merge-check.sh, touching nothing,
|
||||
* is said by its statuses alone, not by a comment on every push. */
|
||||
export function commentFor(c: Checked): string | null {
|
||||
const gate = c.gate ?? { verdict: c.verdict, summary: c.summary };
|
||||
const repo = c["repo-check"];
|
||||
const repoWrong = !!repo && repo.verdict !== "pass" && repo.verdict !== "warning";
|
||||
if (gate.verdict === "pass" && !repoWrong) return null;
|
||||
if (gate.verdict === "pass" && !repoWrong && !builds(c.plan)) 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.dependents?.length ? `; built after them: ${gate.dependents.join(", ")}` : "") + ")" : ""));
|
||||
if (repo) lines.push(`- \`${REPO_CHECK_CONTEXT}\`: **${(repo.verdict || "error").toUpperCase()}** — ${repo.summary}`);
|
||||
if (builds(c.plan)) lines.push("", planText(c.plan!));
|
||||
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\`\`\`` : "";
|
||||
return lines.join("\n") + ran + report;
|
||||
|
||||
@@ -83,3 +83,21 @@ test("a commit status is set on the commit, under the merge check's context", as
|
||||
assert.equal(seen!.body.state, "failure");
|
||||
assert.equal(seen!.body.context, "mesh/merge-gate");
|
||||
});
|
||||
|
||||
test("a change plan is the gate's result: said on the status and, when it builds something, as a comment", async () => {
|
||||
const { statusFor, commentFor } = await import("../pulls.ts");
|
||||
const plan = {
|
||||
repository: "novox/mesh-catalog", base: "main", head: "0123456789abcdef", moved: ["gitea"],
|
||||
tiers: [["gitea"]], machines: [{ machine: "anchor", receives: ["gitea"] }], steps: [],
|
||||
summary: "builds gitea → anchor; no bus step",
|
||||
};
|
||||
const c = { owner: "novox", repo: "mesh-catalog", number: 7, commit: "0123456789abcdef", id: "b", verdict: "pass",
|
||||
summary: "every machine composes", gate: { verdict: "pass", summary: "every machine composes", modules: ["gitea"] }, plan };
|
||||
assert.equal(statusFor(c).description, "pass: builds gitea → anchor; no bus step; every machine composes");
|
||||
const said = commentFor(c) ?? "";
|
||||
assert.match(said, /Change plan\*\* — builds gitea → anchor/);
|
||||
assert.match(said, /- anchor: receives gitea/);
|
||||
// A plan that builds nothing, passing: the statuses say it, no comment.
|
||||
const nothing = { ...c, plan: { ...plan, moved: [], tiers: [], machines: [], summary: "builds nothing" } };
|
||||
assert.equal(commentFor(nothing), null);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user