// What the forge's holder does for a delivery (novox/hq ADR 0239): the commit's note under // refs/notes/mesh-plan, the delivery's view on its pull request, and its statuses — asked by mesh-delivery, // the delivery's owner, through this module's tools. The forge is this module's; mesh-delivery never writes // to it itself. // // **The note is written in the forge's own repository, as the forge's own user.** The forge's API reads a // note and writes none, and a push needs a clone and a credential nobody else should hold; the forge's // container holds the bare repository and git. So the line is appended there, by `git notes append`, as the // account the forge runs as — the mesh's own forge, never a person's or an agent's hand. Appending is // idempotent here: a line the note already has is not added again, so asking twice adds nothing. // // Pure functions and an injectable runner, so this is tested without a forge. import { execFile } from "node:child_process"; import { promisify } from "node:util"; /** How a command is run: docker on the forge's machine, or a test's. */ export type Runner = (file: string, args: string[]) => Promise<{ stdout: string; code: number }>; const execFileP = promisify(execFile); export const run: Runner = async (file, args) => { try { const { stdout } = await execFileP(file, args, { maxBuffer: 4 << 20 }); return { stdout, code: 0 }; } catch (err) { const e = err as { code?: number; stdout?: string }; return { stdout: e.stdout ?? "", code: typeof e.code === "number" ? e.code : 1 }; } }; const name = /^[A-Za-z0-9_.-]+$/; const sha = /^[0-9a-fA-F]{7,64}$/; const ref = /^[a-z0-9][a-z0-9-]*$/; /** A note line as it may be written: one line, bounded, nothing that is not text. */ export function noteLine(line: string): string { const one = String(line ?? "").replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim(); if (!one) throw new Error("an empty line is no note"); return one.length > 2000 ? one.slice(0, 1999) + "…" : one; } /** Where the forge keeps a repository, inside its container. */ export function gitDir(owner: string, repo: string): string { if (!name.test(owner) || !name.test(repo)) throw new Error(`${owner}/${repo} is not a repository's name`); return `/data/git/repositories/${owner.toLowerCase()}/${repo.toLowerCase()}.git`; } /** The commands the note is read and appended with, in the forge's container, as its user. */ export function noteArgs(container: string, owner: string, repo: string, commit: string, notesRef: string, line?: string): string[] { if (!name.test(container)) throw new Error(`${container} is not a container's name`); if (!sha.test(commit)) throw new Error(`${commit} is not a commit`); if (!ref.test(notesRef)) throw new Error(`${notesRef} is not a notes ref`); const base = ["exec", "-u", "git", container, "git", "--git-dir", gitDir(owner, repo), "-c", "user.name=mesh", "-c", "user.email=mesh@mesh.invalid", "notes", `--ref=${notesRef}`]; return line === undefined ? [...base, "show", commit] : [...base, "append", "-m", noteLine(line), commit]; } /** Append a line to a commit's note, unless the note already holds it. Answers whether it was added. */ export async function appendNote(runner: Runner, container: string, owner: string, repo: string, commit: string, notesRef: string, line: string): Promise<{ added: boolean; lines: number }> { const wanted = noteLine(line); const shown = await runner("docker", noteArgs(container, owner, repo, commit, notesRef)); // No note yet is git's exit 1 with nothing on stdout; anything else unreadable is said. const lines = shown.code === 0 ? shown.stdout.split("\n").filter((l) => l.trim() !== "") : []; if (lines.includes(wanted)) return { added: false, lines: lines.length }; const appended = await runner("docker", noteArgs(container, owner, repo, commit, notesRef, wanted)); if (appended.code !== 0) throw new Error(`the note on ${commit.slice(0, 8)} could not be appended (git exited ${appended.code})`); return { added: true, lines: lines.length + 1 }; } /** The marker that makes one comment of a pull request the delivery's view. */ export const VIEW_MARKER = ""; /** The view's body, marked: mesh-delivery writes it, this keeps exactly one of them per pull request. */ export function viewBody(body: string): string { const b = String(body ?? ""); return b.includes(VIEW_MARKER) ? b : `${VIEW_MARKER}\n${b}`; } /** Which comment is the view: the first that carries the marker, or none yet. */ export function viewComment(comments: T[]): T | undefined { return comments.find((c) => c.body.includes(VIEW_MARKER)); } const states = new Set(["pending", "success", "error", "failure", "warning"]); /** The merge check's contexts (pulls.ts): a branch's protection may require them. */ const mergeCheckContexts = new Set(["mesh/merge-gate", "mesh/repo-check"]); /** A status mesh-delivery asks for, checked: one of the forge's states, a context of the mesh's own, a * bounded description. */ export function deliveryStatus(context: string, state: string, description: string, target?: string) { if (!/^mesh\/[a-z-]+$/.test(context)) throw new Error(`${context} is not a status of the mesh's own`); // The merge check's statuses are its verdict's, set when the controller says it, and a branch may require // them: never set by hand, so nothing passes — or blocks — a merge past the check (novox/hq issue 293). if (mergeCheckContexts.has(context)) throw new Error(`${context} is the merge check's status, set by its verdict only`); if (!states.has(state)) throw new Error(`${state} is not a status the forge keeps`); let d = String(description ?? "").replace(/\s+/g, " ").trim(); if (d.length > 140) d = d.slice(0, 139) + "…"; return { state: state as "pending" | "success" | "error" | "failure" | "warning", context, description: d, ...(target && /^https?:\/\//.test(target) ? { target_url: target } : {}) }; }