// mesh-catalog's entrypoint — the module graph's consumer (novox/hq ADR 0070, ADR 0072). // // The build-machine role announces what it built; this places it in the graph and announces what that means. // The control plane hooks the *meaning* — a module was upgraded — rather than the build output, so // it never has to interpret an artifact or ask this module anything. // // **Ordering is not computed here.** When a registration makes something else stale, the modules // whose own dependencies are all current are announced as needing a rebuild; the rest stay stale // and appear the next time round, once whatever they were waiting for is registered. A chain and a // diamond need no special handling, and nothing holds a plan. import { on, emit } from "@novox/mesh-sdk/events"; import { Graph } from "./store.js"; import { BUILD_EVENTS, placeable, type Built } from "./heard.js"; const graph = Graph.fromEnv(); // The schema is not brought up here. The mesh prepares this module's state before it starts this // version, and does not start it if that failed (novox/hq ADR 0135) — see prepare/index.ts. Doing it // at start made a schema that could not be reached a crash loop instead of a stop, with the graph // keeping a gap and nothing saying so. The reason it used to be here — that a step blocking the apply // would block the very apply that brings the overlay up — stopped being true when a step's failure // became this module's business and not the machine's (ADR 0136). /** * What a build means for the graph, wherever it came from. * * Two emitters say the same thing and neither is a mistake: the build machine says it as it happens, * and the control plane says what it already held when this module asks what it missed * (novox/hq ADR 0134). A replay is marked as one in its body, so nothing acts on a module that moved * months ago — see `replay` in heard.ts. */ const placeTheBuild = async (event: { body: unknown }): Promise => { const body = (event.body ?? {}) as Built; const placing = placeable(body); if (placing.placed === "no") { // A check, a try, a dry run and a failure are the build seat's ordinary outcomes: passed over. One // that names no module or commit is a fault in the builder, said rather than dropped, so it does // not look like a missing event. if (placing.fault) console.error(`mesh-catalog: a build event was not placed: ${placing.why}`, body); return; } const { module, commit } = placing; const { upgraded, previous } = await graph.register({ module, commit, repository: body.repository ?? "", path: body.path ?? "", ref: body.ref ?? "", manifest: body.manifest ?? {}, }, body.made ?? [], body.against ?? []); // **A replay is registered and announced to nobody.** See `replay` above: the graph gains what // it was missing, and the mesh is told nothing happened, because nothing did. if (body.replay) return; await emit("registered", { module, commit, upgraded, }); // **A rebuild that changed nothing is not an upgrade.** Announcing it would ripple outward // through modules that did not change, forever (ADR 0072). if (!upgraded) return; await emit("upgraded", { module, commit, previous, }); // What can be built now — stale, and waiting on nothing that is itself stale. for (const next of await graph.buildable()) { await emit("rebuild-needed", { module: next.module, builtAt: next.commit, because: next.because, }); } }; // As it happens — from the build seat that builds now, and the retired one while it still answers — and // what the mesh already held when this module asked what it missed (novox/hq issue 712). for (const heard of BUILD_EVENTS) await on(heard, placeTheBuild); // **And ask for what was built before this catalogue existed** (novox/hq 04-ISSUES/050). // // The queue above is durable, so nothing is missed once this is running. What it cannot have is // what was announced before it first ran — and on a fresh mesh that is never arbitrary: the shared // base, the store this runs on, and this module itself are each necessarily built BEFORE a // catalogue exists to hear about them. The graph's foundation is the part it never sees. // // Asked on every start, not only the first. A catalogue cannot tell whether it has a gap, and the // answer is idempotent: registering a build already held changes nothing and announces nothing. // Asked AFTER subscribing, so a build arriving during the replay is not lost between the two. await emit("catching-up", {});