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
72 lines
3.6 KiB
TypeScript
72 lines
3.6 KiB
TypeScript
// **The forge's announcements carry a message id** (novox/hq ADR 0406 §6 and §7, issue 976). A merge, a new head and a
|
|
// close are announced by more than one path — the poll, the forge's own event, the merge tool's ask of a check — and
|
|
// were kept from being announced twice by files of what had been announced. They are now announced with a message id
|
|
// that is the same for the same announcement, whichever path makes it: the runtime makes an event's `x-event-id` header
|
|
// the bus's message id (mesh-tools node-tools/internal/bus/bus.go, `nats.MsgId`), and the bus stores a message it has
|
|
// seen the id of, within its de-duplication window, once.
|
|
//
|
|
// They are the seat git's events (design 47 *The seat git*), said through the runtime's seat publish with that id.
|
|
//
|
|
// The ids: a merge by its merge commit, a head by the head and the trunk commit it was announced against (the check asked
|
|
// again against a new trunk commit is another announcement), a close by its head.
|
|
|
|
import { sayOnSeat } from "./seatevents.js";
|
|
import { state as keptState } from "@novox/mesh-sdk/state";
|
|
|
|
type Said = { owner?: string; repo?: string; number?: number; head_sha?: string; base_sha?: string; merge_commit_sha?: string };
|
|
|
|
/** The message id of an announcement, or undefined for an event that is not one of these three. */
|
|
export function announcementId(type: string, body: unknown): string | undefined {
|
|
const b = (body ?? {}) as Said;
|
|
const pull = `${b.owner}/${b.repo}#${b.number}`;
|
|
switch (type) {
|
|
case "pull.merged":
|
|
return `merged:${pull}@${b.merge_commit_sha ?? ""}`;
|
|
case "pull.updated":
|
|
return `updated:${pull}@${b.head_sha ?? ""}:${b.base_sha ?? ""}`;
|
|
case "pull.closed":
|
|
return `closed:${pull}@${b.head_sha ?? ""}`;
|
|
default:
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
/** Announce a merge, a head or a close: said on the seat git (seatevents.ts) with its message id. */
|
|
export async function announce(type: string, body: unknown): Promise<void> {
|
|
const id = announcementId(type, body);
|
|
if (!id) throw new Error(`${type} is not an announcement with a message id`);
|
|
if (!(await sayOnSeat(type, (body ?? {}) as Record<string, unknown>, id))) throw new Error(`the seat git says no ${type}`);
|
|
}
|
|
|
|
// --- the watermark: the one value of the old files that is not the bus's to remember --------------------------------
|
|
|
|
/** What the module keeps of how far its poll of merges has looked: a time, in its own state. */
|
|
interface Kept {
|
|
get(key: string): Promise<{ value: unknown } | null>;
|
|
put(key: string, value: unknown): Promise<number>;
|
|
}
|
|
|
|
const WATERMARK = "merges-since";
|
|
|
|
/** The time the poll of merges last looked from, or "" when none is kept or the state cannot be reached: a merge made
|
|
* before it is history, one after it is announced. */
|
|
export async function readWatermark(state: Kept = keptState("announced")): Promise<string> {
|
|
try {
|
|
const entry = await state.get(WATERMARK);
|
|
const since = (entry?.value as { since?: string } | undefined)?.since;
|
|
return typeof since === "string" ? since : "";
|
|
} catch (err) {
|
|
console.error(`[gitea] where the poll of merges last looked from could not be read, so none is kept — ${err instanceof Error ? err.message : err}`);
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/** Keep the time the poll of merges looked from; a state that cannot be reached is said, and the next pass keeps it. */
|
|
export async function keepWatermark(since: string, state: Kept = keptState("announced")): Promise<void> {
|
|
try {
|
|
await state.put(WATERMARK, { since });
|
|
} catch (err) {
|
|
console.error(`[gitea] where the poll of merges looked from could not be kept — ${err instanceof Error ? err.message : err}`);
|
|
}
|
|
}
|