GIT5: the forge's push webhook of a branch says branch-moved with the new head on the seat

Builds: design 47 G §11, §111, §331 branch-moved

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 11:32:33 +01:00
parent 0bf636826a
commit 975f1aa8e8
2 changed files with 23 additions and 6 deletions
+16 -5
View File
@@ -10,9 +10,9 @@
import { seatPublish } from "@novox/mesh-sdk/runtime";
import {
forgeDeleteSubject, forgePullSubject, forgeStatusSubject, pullClosedSubject, pullMergedSubject, pullUpdatedSubject,
repoWritableSubject,
repoWritableSubject, branchMovedSubject,
type Change, type ForgeDeleteEvent, type ForgePullEvent, type ForgeStatusEvent, type PullClosedEvent, type PullMergedEvent,
type PullUpdatedEvent, type RepoWritableEvent,
type PullUpdatedEvent, type RepoWritableEvent, type BranchMovedEvent,
} from "@novox/mesh-delivery-contracts-git";
/** The forge's pull request actions the seat's forge.pull says (the contract's word list). */
@@ -36,8 +36,18 @@ function changeOf(b: Body): Change | undefined {
}
/** The seat's subject and body for one of the module's announcements, or undefined when the seat says no such event. */
export function seatEventOf(type: string, b: Body): { subject: string; body: unknown } | undefined {
export function seatEventOf(type: string, b: Body, now: () => Date = () => new Date()): { subject: string; body: unknown; id?: string } | undefined {
switch (type) {
case "forge.push": {
// A branch moved, by a merge or an admitted push alike (design 47 §11): the forge's push webhook of a branch with a
// head after it. A tag's push, and a branch's deletion (the head is the zero hash), move no branch.
const ref = s(b.ref) ?? "";
const head = s(b.after) ?? "";
if (!ref.startsWith("refs/heads/") || !/^[0-9a-f]{40,64}$/.test(head) || /^0+$/.test(head)) return undefined;
const branch = ref.slice("refs/heads/".length);
const body: BranchMovedEvent = { repository: repositoryOf(b), branch, head, at: now().toISOString() };
return { subject: branchMovedSubject, body, id: `moved:${body.repository}:${branch}@${head}` };
}
case "pull.updated": {
const body: PullUpdatedEvent = { repository: repositoryOf(b), number: Number(b.number), base: String(b.base ?? ""),
branch: String(b.head ?? ""), head: String(b.head_sha ?? ""), ...opt("base-head", s(b.base_sha)),
@@ -82,7 +92,8 @@ export function seatEventOf(type: string, b: Body): { subject: string; body: unk
return undefined;
}
/** The module's own events the seat says instead: never said as the module's (its `emits` names none of them). */
/** The module's own events the seat says instead: never said as the module's (its `emits` names none of them). `forge.push`
* stays the module's own too: the seat's branch-moved is said beside it. */
export const SEAT_ONLY = new Set(["pull.updated", "pull.merged", "pull.closed", "forge.status", "forge.delete", "repo.writable"]);
export type Say = (subject: string, body: unknown, id?: string) => Promise<void>;
@@ -91,6 +102,6 @@ export type Say = (subject: string, body: unknown, id?: string) => Promise<void>
export async function sayOnSeat(type: string, body: Body, id = "", say: Say = seatPublish): Promise<boolean> {
const e = seatEventOf(type, body);
if (!e) return false;
await say(e.subject, e.body, id);
await say(e.subject, e.body, id || e.id);
return true;
}
+7 -1
View File
@@ -45,13 +45,19 @@ test("an announcement is said on the seat git with its id, in the contract's bod
["repo.writable", { owner: "novox", repo: "app", account: "mesh-tools" }, "mesh.seat.git.event.repo-writable"],
["forge.pull-request.synchronized", { full_name: "novox/app", number: 7, head_sha: "aaa111" }, "mesh.seat.git.event.forge-pull"],
["forge.pull-request.labeled", { full_name: "novox/app", number: 7 }, undefined],
["forge.push", { full_name: "novox/app" }, undefined],
["forge.push", { full_name: "novox/app", ref: "refs/tags/v1", after: "a".repeat(40) }, undefined],
["forge.push", { full_name: "novox/app", ref: "refs/heads/gone", after: "0".repeat(40) }, undefined],
["forge.push", { full_name: "novox/app", ref: "refs/heads/feat/x", after: "a".repeat(40) }, "mesh.seat.git.event.branch-moved"],
];
for (const [type, body, subject] of rows) assert.equal(seatEventOf(type, body)?.subject, subject, type);
assert.deepEqual(seatEventOf("pull.updated", updated)?.body, { repository: "novox/app", number: 7, base: "main", branch: "",
head: "aaa111", "base-head": "ttt333" });
assert.equal(seatEventOf("forge.pull-request.edited", { full_name: "novox/app", number: 7, body: "x" })?.body &&
"body" in (seatEventOf("forge.pull-request.edited", { full_name: "novox/app", number: 7, body: "x" })!.body as object), false);
// A push of a branch (a merge and an admitted push alike) says branch-moved with the new head, one id per move.
const moved = seatEventOf("forge.push", { full_name: "novox/app", ref: "refs/heads/main", after: "b".repeat(40) }, () => new Date("2026-11-11T10:00:00Z"));
assert.deepEqual(moved?.body, { repository: "novox/app", branch: "main", head: "b".repeat(40), at: "2026-11-11T10:00:00.000Z" });
assert.equal(moved?.id, `moved:novox/app:main@${"b".repeat(40)}`);
assert.deepEqual([...SEAT_ONLY].sort(), ["forge.delete", "forge.status", "pull.closed", "pull.merged", "pull.updated", "repo.writable"]);
});