Unify trunk on main: initialization → main #3

Merged
jschoubben merged 95 commits from initialization into main 2026-09-05 01:13:46 +00:00
4 changed files with 85 additions and 11 deletions
Showing only changes of commit 88cf89194a - Show all commits
+10 -3
View File
@@ -1,8 +1,10 @@
# One machine, raising a substrate from the bundle its host carries.
#
# This is the bootstrap class (novox/hq ADR 0009): no forge, no control plane, no delivery. It
# exists to develop the first three steps of raising a mesh — a container runtime, a store, and
# a database inside it — which is as far as the bootstrap can go until a control plane exists.
# This is the bootstrap class (novox/hq ADR 0009): no forge, no control plane to talk to, no
# delivery. It exists to develop the steps of raising a mesh on a machine with no route out —
# a container runtime, a store, the control plane's schema in it, and the broker.
#
# It stops before the control plane *runs*, because there is nothing for it to serve yet.
scenario: first-node
segments:
@@ -15,8 +17,13 @@ machines:
at: { segment: hosting, address: [192.0.2.10] }
inbound: allow
# Placed into a registry the scenario raises, which is what a real node pulls from anyway. The
# digests below are the ones that registry assigns, and that satisfies pinning: what is required
# is a reference that is exact and cannot move (novox/hq ADR 0006).
images:
- postgres:17-alpine
- cloudamqp/lavinmq:latest
- mesh-control:development
place:
all: [host, runtime]
+5 -2
View File
@@ -8,7 +8,7 @@
* by the running machine now — nothing is inferred from a file on disk.
*/
import { incusOk, taggedNetworks } from "../incus/client.ts";
import { incus, incusOk, taggedNetworks } from "../incus/client.ts";
import { depthOf, type Diagram, type DiagramMachine, type DiagramSegment } from "./model.ts";
interface RawInstance {
@@ -28,7 +28,10 @@ interface RawInstance {
export async function diagramFromLive(instanceId: string): Promise<Diagram> {
const networks = (await taggedNetworks()).filter((n) => n.instanceId === instanceId);
const json = (await incusOk(["list", "--format", "json"], 30_000)) ?? "[]";
// Not `incusOk(...) ?? "[]"`. A picture is read from what runs (novox/hq ADR 0018), and a read
// that failed and became an empty list would draw an empty scenario rather than fail — a
// diagram that is confidently wrong, which is worse than no diagram.
const json = (await incus(["list", "--format", "json"], 30_000)).stdout.trim() || "[]";
const parsed = JSON.parse(json) as RawInstance[];
const mine = parsed.filter((i) => i.config?.["user.mesh-lab.instance"] === instanceId);
if (mine.length === 0 && networks.length === 0) throw new Error(`no scenario instance '${instanceId}'`);
+29 -6
View File
@@ -111,6 +111,22 @@ export async function incusOk(args: string[], timeoutMs = 60_000): Promise<strin
}
}
/**
* Ask incus what exists, where answering "none" without having looked would be a lie.
*
* The comment on `incusOk` above warns that absence and success must not be made
* indistinguishable. Three of its callers then wrote `?? "[]"` and did exactly that, and it cost
* a session: `mesh-lab list` reported *no scenario instances standing* while two were standing,
* because this shell had no permission to reach the daemon. The lab was not wrong about the
* instances — it had never managed to ask.
*
* So anything enumerating what exists comes through here and throws. `incusOk` remains right for
* questions where failure genuinely means no, like `instanceExists`.
*/
async function enumerate(args: string[], timeoutMs = 30_000): Promise<string> {
return (await incus(args, timeoutMs)).stdout;
}
/** Did the command work? For commands whose output is not the point. */
export async function succeeds(args: string[], timeoutMs = 60_000): Promise<boolean> {
return (await incusOk(args, timeoutMs)) !== null;
@@ -168,14 +184,18 @@ export interface TaggedInstance {
* nothing. Metadata is what the instance actually knows about itself.
*/
export async function taggedInstances(): Promise<TaggedInstance[]> {
const json = (await incusOk(["list", "--format", "json"], 30_000)) ?? "[]";
const json = (await enumerate(["list", "--format", "json"])).trim() || "[]";
let parsed: unknown;
try {
parsed = JSON.parse(json);
} catch {
return [];
throw new IncusError(["list", "--format", "json"],
`incus answered something that is not JSON: ${json.slice(0, 200)}`, null);
}
if (!Array.isArray(parsed)) {
throw new IncusError(["list", "--format", "json"],
"incus answered JSON that is not a list of instances", null);
}
if (!Array.isArray(parsed)) return [];
const tagged: TaggedInstance[] = [];
for (const entry of parsed) {
@@ -199,14 +219,17 @@ export interface TaggedNetwork {
}
export async function taggedNetworks(): Promise<TaggedNetwork[]> {
const json = (await incusOk(["network", "list", "--format", "json"], 30_000)) ?? "[]";
const args = ["network", "list", "--format", "json"];
const json = (await enumerate(args)).trim() || "[]";
let parsed: unknown;
try {
parsed = JSON.parse(json);
} catch {
return [];
throw new IncusError(args, `incus answered something that is not JSON: ${json.slice(0, 200)}`, null);
}
if (!Array.isArray(parsed)) {
throw new IncusError(args, "incus answered JSON that is not a list of networks", null);
}
if (!Array.isArray(parsed)) return [];
const tagged: TaggedNetwork[] = [];
for (const entry of parsed) {
+41
View File
@@ -0,0 +1,41 @@
// Set before importing: the client reads MESH_LAB_INCUS once, at module load.
// `false` is a real program that exits non-zero and prints nothing — which is also the worst
// case, because an empty stderr is how a failure arrives with no explanation.
process.env["MESH_LAB_INCUS"] = "false";
import { test } from "node:test";
import assert from "node:assert/strict";
import { instanceExists, taggedInstances, taggedNetworks } from "../src/incus/client.ts";
/**
* "I cannot see" must never be answered as "there is nothing there."
*
* This is the fault the comment on `incusOk` warns about, committed by three of its own callers
* writing `?? "[]"`. It cost a session: `mesh-lab list` printed *no scenario instances standing*
* while two were standing, because the shell had no permission to reach the daemon. Nothing was
* wrong with the lab's knowledge of the instances — it had never managed to ask.
*
* The same shape as the fault the node host exists to prevent, in the tool that tests the host:
* a service that does not exist reported as `stopped`.
*/
test("listing instances fails rather than reporting none", async () => {
await assert.rejects(
() => taggedInstances(),
"a failed `incus list` came back as an empty list; every caller would report nothing running",
);
});
test("listing networks fails rather than reporting none", async () => {
await assert.rejects(
() => taggedNetworks(),
"a failed `incus network list` came back as an empty list",
);
});
test("but a question whose failure genuinely means no still answers no", async () => {
// The distinction worth keeping. `instanceExists` asks about one named thing, and a daemon
// that will not answer is not evidence the instance exists — so false is honest here, and
// making this throw too would be over-correcting until nothing can be asked at all.
assert.equal(await instanceExists("anything"), false);
});