Feature 57 (config and provisioning): modules declare config and provisions, their files rendered from templates; synced with main (issues of feature 57, 874)

This commit is contained in:
2026-10-16 07:37:43 +00:00
parent 1a2c917abf
commit 9652b2d5ce
771 changed files with 52063 additions and 1774 deletions
+51 -12
View File
@@ -9,7 +9,8 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import {
checkDir, cleanCredentials, clearChecks, credentialArgs, forgeCredential, installPlan, listRuns, npmRegistry, NPM_REPOSITORIES, publicRootOf, readStatus, REPOSITORIES, running, start,
stop, tail, writeCredential,
stop, tail, writeCredential, baseBuildLog, buildingBase, listBaseBuilds, startBaseBuild, credentialPath, launchRun,
launchRebuild, BASE_MAX_AGE_DAYS, describeBase, readBaseImage, baseProblem,
} from "./runs.js";
export function getLabTools(env: NodeJS.ProcessEnv): ToolDefinition[] {
@@ -59,12 +60,18 @@ export function getLabTools(env: NodeJS.ProcessEnv): ToolDefinition[] {
// **And what a run would check out and install, before a run finds out** (novox/hq issue 624): every
// repository it clones answers on the forge, and every one it installs has a lock file where the run
// installs from.
// A credential a stopped, crashed or rebooted run left behind is removed, and said; one still in use
// by a running run is not.
const busy = running(work);
const left = cleanCredentials(work, busy ? `${work}/.${busy.id}.git-credentials` : undefined);
const removed = left.length > 0 ? `removed ${left.length} forge credential(s) a run left behind: ${left.join(", ")}` : "";
const output = [`${check.stdout}${check.stderr}`.trim(), removed, ...leftovers, ...problems].filter(Boolean).join("\n");
// A credential a stopped, crashed or rebooted run or rebuild left behind is removed, and said; one still in
// use by a running run or rebuild is not.
const busy = [running(work)?.id, buildingBase(work)?.id].filter((id): id is string => !!id);
const left = cleanCredentials(work, busy.map((id) => credentialPath(work, id)));
const removed = left.length > 0 ? `removed ${left.length} forge credential(s) a run or rebuild left behind: ${left.join(", ")}` : "";
// **And how old the base image is** (novox/hq issue 698): past the bound, missing, or of an age that cannot
// be read is a problem here, though lab_run refuses only the first.
const base = readBaseImage();
const unfit = baseProblem(base);
if (unfit) problems.push(unfit);
const output = [`${check.stdout}${check.stderr}`.trim(), describeBase(base), removed, ...leftovers, ...problems]
.filter(Boolean).join("\n");
return { ok: check.status === 0 && problems.length === 0, output };
},
},
@@ -96,11 +103,43 @@ export function getLabTools(env: NodeJS.ProcessEnv): ToolDefinition[] {
}
const stranger = Object.keys(refs).filter((r) => !REPOSITORIES.includes(r));
if (stranger.length > 0) return { started: false, reason: `the lab does not build ${stranger.join(", ")}` };
const busy = running(work);
if (busy) return { started: false, reason: `${busy.id} is still ${busy.state}; one run at a time`, running: busy };
const registry = await registryOf(forge);
return { started: true, run: start(work, forge, tests, refs, credentialOf(forge), registry.registry),
registry: registry.registry, ...(registry.problem ? { problem: registry.problem } : {}) };
// **One job at a time, not on a base image past its bound, nor while it is rebuilt** (novox/hq issue 698,
// review 256): the registry asked first, then the check and the start with nothing awaited between.
const launched = await launchRun(work, { registry: () => registryOf(forge), base: () => readBaseImage() },
(registry) => start(work, forge, tests, refs, credentialOf(forge), registry));
if (!launched.started) return launched;
return { started: true, run: launched.job, base: describeBase(launched.base), registry: launched.registry,
...(launched.problem ? { problem: launched.problem } : {}) };
},
},
{
name: "lab_base",
description:
"The lab's base image, which every bed machine with a container runtime is raised from: when it was built, its " +
`age, the bound lab_run refuses past (${BASE_MAX_AGE_DAYS} days), and the last rebuilds with the newest one's log. ` +
"Reads only, unless rebuild is true: then it rebuilds the image with mesh-lab's `base build` from the forge's " +
"main, in the background, and answers at once; ask again for the build's state and the image's new age.",
input: {
rebuild: { type: "boolean", description: "rebuild the image (default false: only read)" },
},
run: async (args) => {
const before = readBaseImage();
const builds = listBaseBuilds(work).slice(0, 3);
const newest = builds[0];
const reading = {
base: before, said: describeBase(before), bound: BASE_MAX_AGE_DAYS,
...(baseProblem(before) ? { problem: baseProblem(before) } : {}),
builds, ...(newest ? { log: baseBuildLog(work, newest.id) } : {}),
};
if (args.rebuild !== true) return reading;
const forge = forgeOf();
if (!forge) return { ...reading, started: false, reason: "the lab's forge is not set: settings for lab, {\"forge\": \"<url>\"}" };
// Not under a run, nor beside another rebuild: the registry asked first, then the check and the start with
// nothing awaited between (review 256).
const launched = await launchRebuild(work, { registry: () => registryOf(forge), base: () => before },
(registry) => startBaseBuild(work, forge, credentialOf(forge), registry));
if (launched.started === false) return { ...reading, started: false, reason: launched.reason };
return { ...reading, started: true, build: launched.job };
},
},
{