Files
mesh-catalog/modules/mesh-catalog/heard.ts
T
jschoubben 35ff67bda0
mesh/delivery-group group feat/774-l3-reland rejected: composed together: the checker's verdict at the members' heads (b48c90fea31f): integration: mesh-self-ch…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: mesh-self-check/cmd/mesh-self-check.TestTheSelfCheckWholeRunsOnTheBus: the run answered {Run:doctor-1793351737-1 At:20…
Design 47's last milestones: the keeper, self-check, healers, hand-acts, provisioner, vault, artifact sweep, disk readings, credential forms (issues 774, 1952, 1953, 1954, 1955)
One commit with exactly the tree of the reviewed head 35b4509c0a, on main 12c2574c9d, so two compiled programs added and removed on the branch leave no trace in history (issue 1577).
2026-10-30 02:21:51 +01:00

152 lines
6.6 KiB
TypeScript

// What the catalogue makes of a build outcome it hears (novox/hq issue 712).
//
// Kept apart from index.ts, which subscribes at its top level, so the decision can be tested without a bus.
import type { Made } from "./store.js";
/** What the build seat says when it has built something, and what the control plane replays. */
export interface Built {
module?: string;
commit?: string;
repository?: string;
path?: string;
ref?: string;
manifest?: unknown;
/** What this build published, each pinned as anything else would name it. */
made?: Made[];
/**
* Every artifact this was built on top of, so the edge is derived rather than declared.
*
* References, not module names: the builder sees a pinned image and cannot see which module
* produced it. Turning that into an edge between module-versions is this module's job.
*/
against?: string[];
/**
* This is history, not news — the mesh re-announcing a build this catalogue was not there for.
*
* Registered exactly as any other, and announced as nothing. A module that moved months ago is
* not something anything should act on now: emitting `upgraded` would have the control plane
* decide about a rollout, and `rebuild-needed` would ask for builds of things that are already
* current.
*/
replay?: boolean;
/** Why the build failed; a failed build made no module-version. */
failed?: string;
/** A dry run is looked at, never taken in (novox/hq issue 240). */
"dry-run"?: boolean;
/** A pull request's merge check or a branch tried before one (to-be 45 §9, issue 686): nothing merged. */
"check-outcome"?: unknown;
check?: unknown;
/** The repository's default branch as the build seat read the forge at the build (ADR 0238); empty from a seat older than the rule. */
trunk?: string;
/** Whether the commit is on `trunk`. */
"on-trunk"?: boolean;
/** The branches the commit is on. */
branches?: string[];
}
/**
* Where a build outcome comes from now: the builder's log (novox/hq ADR 0571 clauses 1 and 9). The module asks the
* builder's tool for the builds that ended since a time, and again whenever the builder's state of done keys changes.
*/
export const BUILDER = "mesh-delivery-builder";
export const BUILDS_SINCE_TOOL = "builder_builds_since";
export const BUILDS_STATE = "mesh-delivery-builder.builds";
export const MANIFESTS_STATE = "mesh-delivery-indexer.manifests";
/** One entry of the builder's log, an ended build (mesh-sdk delivery.BuildLogEntry). */
export interface LogEntry {
event: string;
module: string;
fp: string;
repository: string;
directory: string;
commit: string;
ref?: string;
node?: string;
at: string;
/** done, failed or queued. */
outcome?: string;
/** The digest of the manifest built, read from the indexer's state of manifests. */
manifest?: string;
against?: string[];
made?: Made[];
}
/** The answer of the builder's tool: every ended build since a time, oldest first, and whether the log held more. */
export interface BuildsSince {
since: string;
builds: LogEntry[];
more?: boolean;
}
/**
* What a tool answers, whether bare or wrapped as the runtime wraps a tool's reply ({output} or {content:[{text}]}).
*/
export function answerOf<T>(raw: unknown): T {
const r = raw as { output?: unknown; content?: { text?: string }[] } | null;
if (r && typeof r.output === "string") return JSON.parse(r.output) as T;
if (r && Array.isArray(r.content) && typeof r.content[0]?.text === "string") return JSON.parse(r.content[0].text) as T;
if (typeof raw === "string") return JSON.parse(raw) as T;
return raw as T;
}
/**
* A log entry as the build outcome the catalogue places: a build that is done is placed with its manifest, what it
* made and what it was built against; one that failed is passed over (placeable says so); one still queued has not
* ended and is not taken in. `replay` is set by the caller for history.
*/
export function builtFrom(e: LogEntry, manifest: unknown, replay: boolean): Built | undefined {
if (e.outcome === "queued") return undefined;
return {
module: e.module,
commit: e.commit,
repository: e.repository,
path: e.directory,
ref: e.ref ?? "",
manifest,
made: e.made ?? [],
against: e.against ?? [],
replay,
...(e.outcome === "failed" ? { failed: "the builder's log says the build failed" } : {}),
};
}
/**
* Whether a build outcome is a module-version to place in the graph, and why not when it is not.
*
* It mirrors two of the rules the controller keeps before it registers a build (mesh-controller
* cmd/mesh-controller/main.go `builds.Built`, build.go `publishable`): a merge check, a try and a dry run are
* never published, and **only a commit on its repository's trunk is** (ADR 0238) — a branch built through
* the `build` verb is checked, never a module's version. Where the seat names no trunk (one older than the
* rule) it is placed, as the controller lets it through. A failure made nothing and is passed over too.
*
* Not mirrored, because the catalogue cannot see what they read: the branch a module follows when it is not
* the default one (the controller's `follows`; such a module's builds off the default branch are passed over
* here and reach the graph by the replay), ADR 0266's rule that a module registers only from the repository
* it is built from, a superseded request, a build in the scratch namespace, one that failed its gate, and a
* build whose source is unchanged. Each needs the controller's records. This is safe for what the mesh
* sends, which is chosen from the controller's own module table and never from this graph; that the graph
* can still hold a build the controller refused under those rules is a follow-up (hq issue 712).
*/
export function placeable(body: Built):
| { placed: "yes"; module: string; commit: string }
| { placed: "no"; why: string; fault: boolean } {
if (body["check-outcome"] != null || body.check != null) {
return { placed: "no", why: "a merge check or a try: nothing of it merged", fault: false };
}
if (body["dry-run"]) {
return { placed: "no", why: "a dry run is looked at, never taken in", fault: false };
}
if (body.failed) {
return { placed: "no", why: "a failed build made no module-version", fault: false };
}
if (body.trunk && !body["on-trunk"] && !(body.branches ?? []).includes(body.trunk)) {
return { placed: "no", why: `the commit is not on its repository's trunk ${body.trunk}: checked, never published (ADR 0238)`, fault: false };
}
if (!body.module || !body.commit) {
return { placed: "no", why: "it named no module or no commit", fault: true };
}
return { placed: "yes", module: body.module, commit: body.commit };
}