Files
mesh-catalog/modules/gitea/delivery.ts
T
jschoubben 32655b95f6 GIT1, GIT2, GIT3, GIT10: the forge module serves the seat git's verbs and says its events, retires its acting tools, follows the catalog half
The four share files (index.ts, module.json, guard.test.ts), so they land as one commit.
GIT1: seat.ts serves pull, open-pulls, tags, trees, file, contains, statuses, status, comment, view, note, merge-pull
as tools on the seat, each answering the contract's shape and an error of kind refused, not-found or failed; a comment,
view or note whose act id is held is answered held (note matched by id). merge-pull merges the head the caller names
(its from-G judgements are GIT6, GIT7, GIT9).
GIT2: seatevents.ts says pull-updated, pull-merged, pull-closed, forge-pull, forge-status, forge-delete and repo-writable on
mesh.seat.git.event.<event>; the module's emits drop them.
GIT3: merge.ts, check.ts, the acting tools, the owed watch, the gate's source read and check-gates-merge are gone.
GIT10: catalog.ts follows the seat mesh-catalog (revision, catalog-revision); no write is granted before the first view.

Builds: design 47 G §310, §311, §312, §313, §314, §163, §317, §278, §321, §196

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 11:31:24 +01:00

85 lines
4.7 KiB
TypeScript

// What the forge's holder does for a delivery (novox/hq ADR 0239): the commit's note under
// refs/notes/mesh-plan and the delivery's view on its pull request, asked through the seat git's verbs `note` and
// `view` (seat.ts). The forge is this module's; no delivery part 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, heldId?: 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() !== "") : [];
// A line that carries the act's id is the act already done, whatever else it says.
if (lines.includes(wanted) || (heldId && lines.some((l) => l.includes(`[${heldId}]`)))) 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 = "<!-- mesh-delivery:view -->";
/** 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<T extends { id: number; body: string }>(comments: T[]): T | undefined {
return comments.find((c) => c.body.includes(VIEW_MARKER));
}