@mark1russell7/harness 0.14.1 → 0.14.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -27,10 +27,17 @@ A **cell** (`cell.ts`) is all that one measurement needs, as data:
27
27
  - Each hop of the topology, on the network of each of its sides.
28
28
  - An injector sidecar in the namespace of the target, with NET_ADMIN. Thus the image of the target stays stock.
29
29
  - The manifest of each environment gives the HZ of its kernel, from /proc/config.gz. A host without that file (a hosted runner) gets HZ from two readings of /proc/timer_list, one second apart: its jiffies against the time of the kernel (`hzOf`). `hzFrom` tells which.
30
+ - The manifest of each environment holds a `substrate` block (`substrate.ts`). It gives the layer, the cloud, the vendor, the kernel, HZ, KVM and the image of the runner. GitHub can schedule one image on Azure or on AWS. Thus the cloud comes from the DMI vendor, not from the name of the kernel. No statistic pools two substrates (`comparable`). A record from before the block reads through `substrateOf`, and nothing rewrites it.
30
31
  - The prober ([`@mark1russell7/prober`](https://www.npmjs.com/package/@mark1russell7/prober)): a privileged container that loads the kernel program. It watches the namespace of the client, of the target and of each hop. Each run keeps its records from its start to its end. A run whose records the prober lost is void.
31
32
 
32
33
  Each command is an argv array.
33
34
 
35
+ ### A lab in a guest
36
+
37
+ The folder `vm/`, the file `src/vm.ts` and the workflow `vm.yml` operate a lab in a guest of QEMU, layer E4 of `docs/S7.md`. `src/vm.ts` builds a pinned kernel and a disk, then boots that kernel directly, with no boot loader. The guest has systemd, sshd and the Docker of the runner. The images build in the guest, because the prober compiles against the BTF of the kernel that it operates on.
38
+
39
+ `runCells` in that file is the one command of the cells for the runner and for a guest. Rule 3 of that document pairs the two in one job. A guest cannot read its runner, thus the runner gives its host in `LAB_RUNNER`. `substrateHere` writes E4 with that cloud and image, and with the kernel and HZ of the guest. A run also records `steal`, the share of the CPU that the host took (`stealShare`). A guest sees only the host above it, thus this number does not show the layer below a hosted runner.
40
+
34
41
  ## The topologies
35
42
 
36
43
  A topology (`Topology`) is the chain of hops from the client to the target, as data. Each hop is on two networks, one for each side. Network j joins element j and element j + 1: the client is element 0, and the target is the last element.
@@ -58,6 +65,10 @@ The `reset-both` form sends its resets on the DESTROY event. Each reset has the
58
65
 
59
66
  The lines of each router (`Lab.middlebox`) are the conntrack events and the resets that it sent, on CLOCK_MONOTONIC. Each run keeps the lines from its start to its end.
60
67
 
68
+ A fabric is a topology with no hops and with `fabric` (`fabric.ts`, layer E3 of `docs/S7.md`). Four routers of FRRouting, two leaves and two spines, run eBGP over veth pairs of containerlab. The client hangs off one leaf and the target off the other, each on a new device `eth1`. Each link has the MTU of a Docker network, thus a segment is as long as on E1. Each leaf prefers spine1 by a weight, and spine2 holds the other path.
69
+
70
+ The fault `silence` drops each packet of spine1 in one commit, and its links stay up. A route monitor on each leaf records each change of a route. The later move of the two leaves ends the stall of the run (`Run.heal`), and the verdict of a stall reads it. A move outside the band of the cell makes the run void. Before each run, `ready` waits for each session, for the route on spine1 and for the path on spine2. The record of the run keeps the routes and the count of established sessions (`Run.fabric`).
71
+
61
72
  ## The groups
62
73
 
63
74
  A group (`Group`, `Cell.group`) is a replicated group under an external failover authority, as data. It is a primary and its replicas, and the Sentinels that decide which member is the primary. A group cell has no hops, and the group replaces its target.
@@ -103,6 +114,16 @@ A step-down holds its old primary out of the elections for 60 s. Thus before eac
103
114
 
104
115
  A fault names a member by its role at the start of a run: `primary`, or `replica` for a secondary. A cut from one member (`one`, `one-way`) cuts the first other member in the sequence of the set.
105
116
 
117
+ ### The recovery drills
118
+
119
+ A drill (`Drill`, `Cell.drill`) stops a replica set during writes, removes a disk, and recovers. `drillOnce` in `drill.ts` does it, and `recovery.ts` holds the pure parts.
120
+
121
+ - **The backups** are real. A `mongodump --oplog` and each slice of the oplog go to a host of their own (`ReplicaSetLab.vault`). `bsondump` reads each backup. The record keeps the last write that each backup holds (`RunBackup`).
122
+ - **The fault** comes at `age` after that last write, on the monotonic clock. Thus a run sets the age of its newest backup.
123
+ - **A restore** cannot have a client of the set, because a client writes to any primary. So each slice joins the oplog of the dump, and a server with no set takes the restore on the next port. The set then starts again. A second client (`--from`) sends the writes after the restore, thus no electionId of the old set stops it.
124
+ - **An election** removes only the disk of the primary. The primary comes back at the end of the run, thus the set converges.
125
+ - **The measure** is a set difference (`recoveryOf`). Each rule that a run breaks is a finding (`recoveryFindings`). A finding is a hole below a kept write, lost writes that the last backup does not explain, or tenant data that changed.
126
+
106
127
  ## The clusters of RabbitMQ
107
128
 
108
129
  A cluster (`Cluster`, `Cell.cluster`) is a cluster of RabbitMQ nodes with one quorum queue, as data. The members of the queue elect its leader by Raft, one member on each node. The cluster replaces the target of its cell.
@@ -392,6 +413,8 @@ The distribution test needs a simulator, which does not exist at this time.
392
413
  - A cell with `fresh` also does a dry warm-up: its fault can change the environment for the runs after it. Behind a black hole, Pgpool-II can detach its only server, and then it refuses each new client. Such a cell has an environment for each run.
393
414
  - The `concurrency` of a cell sets how many environments operate at the same time, each with its own prober.
394
415
  - The manifest holds the kernel release, the architecture, the boot id and the Docker version. It also holds the image ids, the packages of the image, the client and the sysctls as read back.
416
+ - Each image that the harness builds has a build record in the manifest (`BuildRecord`). It gives the tag, the bases by digest and the sha256 of each lockfile. It also gives the versions of the packages that the manifests of the image name. The harness, the client and the image of a subject have one. The harness record also has the version of the package.
417
+ - `manifestProblems(manifest)` gives each image of a manifest that does not pin its programs. Such an image is a pulled image without a digest, or a build without its build record. `commitProblems(commit)` examines the commit of a record. A record without a pin for each program is not a record, because nobody can reproduce its numbers.
395
418
  - A cell whose probe names a `driver` opens its labs with the client of that driver (`openLab(target, "ioredis")`).
396
419
  - The harness sets the `targetSettings` of a cell in the target before a client connects, with the `settings` commands of the target (`redis-cli CONFIG SET`, then `CONFIG GET`).
397
420
  - A group cell opens a group environment (`openGroupLab`), and each run is `groupOnce`. Its warm-up is a whole run with its fault, because no fault of a group ends its environment. The manifest also holds the settings of each member and of each Sentinel, as each part read them.
@@ -59,8 +59,13 @@ import { KernelRecord } from "@mark1russell7/prober";
59
59
  * - `drain` puts the node of the part in maintenance mode: `rabbitmq-upgrade drain`. The node closes the
60
60
  * connections of its clients, and it moves the leadership of each of its queues to another node.
61
61
  * `rabbitmq-upgrade revive` ends it after the run.
62
+ *
63
+ * This fault is of a fabric (`Topology.fabric`):
64
+ * - `silence` drops each packet of spine1 in one commit, and its links stay up. Each leaf loses its
65
+ * neighbor only by a detector of its own, and the routes move to spine2. The measured move ends
66
+ * the stall of the client (`Run.heal`).
62
67
  */
63
- type Fault = "blackhole" | "freeze-process" | "rst" | "stall" | "fin" | "unreachable" | "simplex" | "mtu-blackhole" | "netem" | "middlebox-forget" | "proxy-idle" | "toxic" | "server-close" | "partition" | "planned-failover" | "app-stall" | "step-down" | "drain" | "hop-event" | "none";
68
+ type Fault = "blackhole" | "freeze-process" | "rst" | "stall" | "fin" | "unreachable" | "simplex" | "mtu-blackhole" | "netem" | "middlebox-forget" | "proxy-idle" | "toxic" | "server-close" | "partition" | "planned-failover" | "app-stall" | "step-down" | "drain" | "hop-event" | "silence" | "none";
64
69
  /**
65
70
  * A middlebox form tells what a router does with a flow that it forgot, when a late packet comes:
66
71
  * - `reset-both` sends a reset to the two ends when it forgets the flow.
@@ -185,13 +190,33 @@ interface HopWrite {
185
190
  * of the side of the client, and the network of the side of the target.
186
191
  */
187
192
  type Hop = RouterHop | ProxyHop;
193
+ /**
194
+ * A fabric is a routed network on layer E3 (`fabric.ts`), by its timers. The detector of a fabric
195
+ * is the BGP hold timer, or BFD when it has `bfd`.
196
+ */
197
+ interface Fabric {
198
+ readonly keepalive: Quantity;
199
+ readonly hold: Quantity;
200
+ /** These are the receive and transmit interval and the multiplier of BFD, or null for no BFD. */
201
+ readonly bfd: {
202
+ readonly interval: Quantity;
203
+ readonly multiplier: number;
204
+ } | null;
205
+ }
206
+ /** A fabric manifest tells what a fabric ran on: its timers, its image and containerlab. */
207
+ interface FabricManifest extends Fabric {
208
+ readonly image: string;
209
+ readonly containerlab: string;
210
+ }
188
211
  /**
189
212
  * A topology is the chain of hops between the client and the target, in sequence from the client.
190
- * Without hops, the client and the target are a direct pair.
213
+ * Without hops, the client and the target are a direct pair. With `fabric`, a routed network
214
+ * joins them, and the chain of hops is empty.
191
215
  */
192
216
  interface Topology {
193
217
  readonly name: string;
194
218
  readonly hops: readonly Hop[];
219
+ readonly fabric?: Fabric;
195
220
  }
196
221
  /**
197
222
  * A group is a replicated group under an external failover authority, as data. It is a primary and
@@ -257,6 +282,28 @@ interface ReplicaSet {
257
282
  /** This line tells that a member serves. */
258
283
  readonly ready: string;
259
284
  }
285
+ /**
286
+ * A drill is a recovery drill of a replica set cell, as data. The store gets generated data for
287
+ * each tenant and real backups while a client writes. Then the fault removes the disk of a member,
288
+ * or of each member, and the store recovers. A drill shortens the interval of the backups. The
289
+ * loss of a backup depends on the age of the newest backup at the fault, and `age` sets that age.
290
+ */
291
+ interface Drill {
292
+ /** This count is the number of tenants. The size of the store follows it. */
293
+ readonly tenants: number;
294
+ /** This size is the generated data of one tenant. */
295
+ readonly perTenant: Quantity;
296
+ /** This count is the number of slices of the oplog after the dump. */
297
+ readonly slices: number;
298
+ /** This time is the interval from the dump to the first slice, and between two slices. */
299
+ readonly sliceEvery: Quantity;
300
+ /** This time is the age of the newest backup at the fault: from its last write, or from its end in the control. */
301
+ readonly age: Quantity;
302
+ /** This word tells how the store recovers. An election follows the loss of the disk of the primary. A restore from the backups follows the loss of each disk. */
303
+ readonly recovery: "election" | "restore";
304
+ /** This flag makes the drill a control: the client stops before the dump, thus no write comes after it. */
305
+ readonly quiet?: true;
306
+ }
260
307
  /**
261
308
  * A cluster is a cluster of RabbitMQ nodes with one quorum queue, as data. The members of the queue
262
309
  * elect its leader by Raft, one member on each node. Each node is a container from the same stock
@@ -995,6 +1042,8 @@ interface Cell {
995
1042
  readonly writeConcern?: "majority" | 1;
996
1043
  /** This flag makes the fault hold before the client starts. The client then meets it at its first selection. */
997
1044
  readonly before?: boolean;
1045
+ /** This drill makes the cell a recovery drill. It needs `replicaSet`, and its runs have two clients when the store restores. */
1046
+ readonly drill?: Drill;
998
1047
  /** This count is the number of runs of the cell (11), across new environments (two at least). */
999
1048
  readonly runs: number;
1000
1049
  readonly environments: number;
@@ -1108,6 +1157,12 @@ interface Run {
1108
1157
  * its window is δ_abs of its cell: 1 ms on a calibrated runner.
1109
1158
  */
1110
1159
  readonly jitter?: Jitter;
1160
+ /**
1161
+ * This share is the CPU time from 0 to 1 that the host took from this machine in the run, from
1162
+ * `/proc/stat` (`stealShare`). A run of a plain cell writes it. A group, a replica set or a cluster
1163
+ * writes it with its first cell on layer E4.
1164
+ */
1165
+ readonly steal?: number;
1111
1166
  /** These are the addresses and the namespaces of a topology, which the measurement of a run reads. */
1112
1167
  readonly topology?: RunTopology;
1113
1168
  /** These are the lines of the routers of a topology from the start to the end of the run. */
@@ -1116,6 +1171,29 @@ interface Run {
1116
1171
  readonly group?: RunGroup;
1117
1172
  /** These are the lines of a target that writes them (`Target.lines`), from the start to the end of the run. */
1118
1173
  readonly server?: readonly ProbeEvent[];
1174
+ /** This record is the fabric of a cell with `Topology.fabric`. */
1175
+ readonly fabric?: RunFabric;
1176
+ }
1177
+ /** A run route is one change of a route at a leaf of a fabric, on the monotonic clock. */
1178
+ interface RunRoute {
1179
+ readonly leaf: string;
1180
+ readonly t: number;
1181
+ readonly deleted: boolean;
1182
+ readonly prefix: string;
1183
+ readonly via: string | null;
1184
+ readonly dev: string | null;
1185
+ }
1186
+ /**
1187
+ * A run fabric is what the routers did in a run. It holds each change of a route. It also holds the
1188
+ * count of established sessions of each leaf at the start and the end. A session that went down and
1189
+ * came back in the run raises its count.
1190
+ */
1191
+ interface RunFabric {
1192
+ readonly routes: readonly RunRoute[];
1193
+ readonly established: {
1194
+ readonly before: Readonly<Record<string, number>>;
1195
+ readonly after: Readonly<Record<string, number>>;
1196
+ };
1119
1197
  }
1120
1198
  /** A Sentinel event is one line of the observer of a group: an event that a Sentinel published, on the clock of the VM. */
1121
1199
  interface SentinelEvent {
@@ -1152,6 +1230,41 @@ interface RunGroup {
1152
1230
  readonly after: string | null;
1153
1231
  /** These are the sequence numbers that the group holds after the run, when it converged, or null. */
1154
1232
  readonly holds: readonly number[] | null;
1233
+ /** This record tells what a recovery drill did. */
1234
+ readonly drill?: RunDrill;
1235
+ }
1236
+ /** A run backup is one backup of a drill: a dump with its oplog, or one slice of the oplog. */
1237
+ interface RunBackup {
1238
+ readonly kind: "dump" | "slice";
1239
+ /** These are the start and the end of the backup, on the wall clock of the runner, in ms. */
1240
+ readonly started: number;
1241
+ readonly ended: number;
1242
+ /** This write is the last one that the backups hold up to this backup, or null with no write. Its time is the time that the client sent it. */
1243
+ readonly last: {
1244
+ readonly n: number;
1245
+ readonly t: number;
1246
+ } | null;
1247
+ /** This size is the size of the backup on disk. */
1248
+ readonly size: Quantity;
1249
+ }
1250
+ /** A run drill is what a recovery drill did in one run. */
1251
+ interface RunDrill {
1252
+ /** This text is the first line of `mongodump --version`. */
1253
+ readonly tool: string;
1254
+ /** This word tells where the data of the tenants came from. It is always generated data. */
1255
+ readonly seed: "generated";
1256
+ readonly backups: readonly RunBackup[];
1257
+ /** This hash is the hash of the data of the tenants before the first client. */
1258
+ readonly before: string;
1259
+ /** This hash is the hash of the data of the tenants after the run, or null when the set did not converge. */
1260
+ readonly after: string | null;
1261
+ /** These are the start and the end of the restore, on the wall clock of the runner, in ms, or null without a restore. */
1262
+ readonly restored: {
1263
+ readonly started: number;
1264
+ readonly ended: number;
1265
+ } | null;
1266
+ /** This number is the first sequence number of the second client, or null with one client. */
1267
+ readonly second: number | null;
1155
1268
  }
1156
1269
  /**
1157
1270
  * A run topology tells where each socket of a run is, in a topology:
@@ -1169,5 +1282,5 @@ interface RunTopology {
1169
1282
  }>>;
1170
1283
  }
1171
1284
  //#endregion
1172
- export { StageBand as $, PinnedImage as A, Replies as B, Jitter as C, MiddleboxExpectation as D, MiddleboxEvent as E, Reconnects as F, RunGroup as G, RouterHop as H, Recovers as I, SentinelEvent as J, RunPart as K, Reference as L, ProbeEvent as M, ProxyHop as N, MiddleboxForm as O, ProxyTime as P, Stage as Q, ReplicaSet as R, ImageSource as S, LineMatch as T, RttRange as U, RequestPrediction as V, Run as W, Shipped as X, SentinelStage as Y, ShippedImage as Z, HopEvent as _, Cluster as a, isHopPlace as at, HopWrite as b, DriverBand as c, FaultPlace as d, Subject as et, Group as f, Hop as g, GroupRole as h, ClientImage as i, WsProbe as it, Prediction as j, Observer as k, DriverStep as l, GroupPrediction as m, Cell as n, Topology as nt, ClusterClientAt as o, latestEndOf as ot, GroupPlace as p, RunTopology as q, Client as r, Toxic as rt, ClusterStage as s, BuildImage as t, Target as tt, Fault as u, HopPlace as v, KernelRecord as w, HttpProbe as x, HopSignal as y, ReplicaSetStage as z };
1173
- //# sourceMappingURL=cell-BccWKiXh.d.ts.map
1285
+ export { RunRoute as $, MiddleboxExpectation as A, Reference as B, HopWrite as C, KernelRecord as D, Jitter as E, ProbeEvent as F, RouterHop as G, ReplicaSetStage as H, ProxyHop as I, RunBackup as J, RttRange as K, ProxyTime as L, Observer as M, PinnedImage as N, LineMatch as O, Prediction as P, RunPart as Q, Reconnects as R, HopSignal as S, ImageSource as T, Replies as U, ReplicaSet as V, RequestPrediction as W, RunFabric as X, RunDrill as Y, RunGroup as Z, GroupPrediction as _, Cluster as a, Stage as at, HopEvent as b, Drill as c, Target as ct, Fabric as d, WsProbe as dt, RunTopology as et, FabricManifest as f, isHopPlace as ft, GroupPlace as g, Group as h, ClientImage as i, ShippedImage as it, MiddleboxForm as j, MiddleboxEvent as k, DriverBand as l, Topology as lt, FaultPlace as m, Cell as n, SentinelStage as nt, ClusterClientAt as o, StageBand as ot, Fault as p, latestEndOf as pt, Run as q, Client as r, Shipped as rt, ClusterStage as s, Subject as st, BuildImage as t, SentinelEvent as tt, DriverStep as u, Toxic as ut, GroupRole as v, HttpProbe as w, HopPlace as x, Hop as y, Recovers as z };
1286
+ //# sourceMappingURL=cell-B3-BSTit.d.ts.map
@@ -1,12 +1,125 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { execFile } from "node:child_process";
3
- import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
4
- import { createRequire } from "node:module";
5
3
  import { tmpdir } from "node:os";
6
4
  import { dirname, join, posix, relative, resolve } from "node:path";
5
+ import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
6
+ import { createRequire } from "node:module";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { isDeepStrictEqual } from "node:util";
9
+ //#region src/locks.ts
10
+ /**
11
+ * These are the names of the lockfiles that the harness records. A `go.mod` is the lockfile of a Go
12
+ * module, because its `require` lines give exact versions. For them, Go selects the same packages
13
+ * each time, and its checksum database examines each package.
14
+ */
15
+ const LOCK_NAMES = /* @__PURE__ */ new Set([
16
+ "package-lock.json",
17
+ "npm-lock.json",
18
+ "npm-shrinkwrap.json",
19
+ "pnpm-lock.yaml",
20
+ "yarn.lock",
21
+ "bun.lock",
22
+ "Cargo.lock",
23
+ "go.mod",
24
+ "go.sum",
25
+ "jars.sha256"
26
+ ]);
27
+ const nameOf = (at) => at.split("/").at(-1);
28
+ const folderOf = (at) => at.split("/").slice(0, -1).join("/");
29
+ const textOf = (content) => typeof content === "string" ? content : content.toString("utf8");
30
+ /** This function gives each lockfile of a context by its path, with the sha256 of its content in hex. */
31
+ const locksOf = (files) => Object.fromEntries(files.filter((file) => LOCK_NAMES.has(nameOf(file.at))).map((file) => [file.at, createHash("sha256").update(file.content).digest("hex")]).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
32
+ /** This function gives the version of each direct dependency of an npm lockfile (lockfileVersion 2 or 3). */
33
+ const npmVersions = (lock) => {
34
+ const packages = JSON.parse(lock).packages ?? {};
35
+ const root = packages[""] ?? {};
36
+ const names = [...Object.keys(root.dependencies ?? {}), ...Object.keys(root.optionalDependencies ?? {})];
37
+ const found = {};
38
+ for (const name of names) {
39
+ const version = packages[`node_modules/${name}`]?.version;
40
+ if (version !== void 0) found[name] = version;
41
+ }
42
+ return found;
43
+ };
44
+ /** This function gives the packages of a Cargo.lock: the name, the version and the dependencies of each. */
45
+ const cargoPackages = (lock) => lock.split(/^\[\[package\]\]\s*$/m).slice(1).flatMap((block) => {
46
+ const name = /^name = "([^"]+)"$/m.exec(block)?.[1];
47
+ const version = /^version = "([^"]+)"$/m.exec(block)?.[1];
48
+ if (name === void 0 || version === void 0) return [];
49
+ return [{
50
+ name,
51
+ version,
52
+ dependencies: [...(/^dependencies = \[([^\]]*)\]/m.exec(block)?.[1] ?? "").matchAll(/"([^"]+)"/g)].map((match) => match[1])
53
+ }];
54
+ });
55
+ /**
56
+ * This function gives the version of each direct dependency of the root package of a Cargo.lock: the
57
+ * package that the Cargo.toml beside it names. An entry of a dependency list is a name, or a name
58
+ * and a version when the lock holds two versions of that name.
59
+ */
60
+ const cargoVersions = (lock, manifest) => {
61
+ const rootName = /^\[package\][^[]*?^name\s*=\s*"([^"]+)"/ms.exec(manifest)?.[1];
62
+ const packages = cargoPackages(lock);
63
+ const root = packages.find((each) => each.name === rootName);
64
+ if (root === void 0) return {};
65
+ const found = {};
66
+ for (const entry of root.dependencies) {
67
+ const [name, version] = entry.split(" ");
68
+ const pinned = version ?? packages.find((each) => each.name === name)?.version;
69
+ if (name !== void 0 && pinned !== void 0) found[name] = pinned;
70
+ }
71
+ return found;
72
+ };
73
+ /** This function gives the version of each direct requirement of a go.mod. A requirement that is `// indirect` is not direct. */
74
+ const goVersions = (mod) => {
75
+ const found = {};
76
+ const lines = mod.replace(/^require\s*\(([^)]*)\)/gm, (_all, block) => block.split(/\r?\n/).map((line) => `require ${line.trim()}`).join("\n"));
77
+ for (const line of lines.split(/\r?\n/)) {
78
+ const match = /^require\s+(\S+)\s+(v\S+)(.*)$/.exec(line.trim());
79
+ if (match === null || /\/\/\s*indirect/.test(match[3])) continue;
80
+ found[match[1]] = match[2];
81
+ }
82
+ return found;
83
+ };
84
+ /** This function gives the version of each jar of a list of jars by their SHA-256: `<hex> HikariCP-7.1.0.jar` gives HikariCP 7.1.0. */
85
+ const jarVersions = (list) => {
86
+ const found = {};
87
+ for (const line of list.split(/\r?\n/)) {
88
+ const match = /^[0-9a-f]{64}\s+\*?(.+?)-(\d[^-]*(?:-[A-Za-z0-9.]+)?)\.jar$/.exec(line.trim());
89
+ if (match !== null) found[match[1]] = match[2];
90
+ }
91
+ return found;
92
+ };
93
+ /**
94
+ * This function gives the version of each package that the manifests of a context name directly,
95
+ * by its name. It reads each npm lockfile, Cargo.lock, go.mod and list of jars. A package of a
96
+ * workspace in the context is a package of the image too, with the version of its package.json.
97
+ * Such a package has a name and a version, and it is not private. The record has the sha256 of
98
+ * the lockfile of a pnpm, yarn or bun workspace (`locksOf`). This function does not read versions
99
+ * from such a lockfile.
100
+ */
101
+ const versionsOf = (files) => {
102
+ const byPath = new Map(files.map((file) => [file.at, textOf(file.content)]));
103
+ const found = {};
104
+ for (const [at, text] of [...byPath].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
105
+ const name = nameOf(at);
106
+ const folder = folderOf(at);
107
+ const beside = (other) => byPath.get(folder === "" ? other : `${folder}/${other}`);
108
+ if (name === "package-lock.json" || name === "npm-lock.json" || name === "npm-shrinkwrap.json") Object.assign(found, npmVersions(text));
109
+ else if (name === "Cargo.lock") Object.assign(found, cargoVersions(text, beside("Cargo.toml") ?? ""));
110
+ else if (name === "go.mod") Object.assign(found, goVersions(text));
111
+ else if (name === "jars.sha256") Object.assign(found, jarVersions(text));
112
+ else if (name === "package.json") {
113
+ const manifest = JSON.parse(text);
114
+ if (manifest.private !== true && manifest.name !== void 0 && manifest.version !== void 0) found[manifest.name] = manifest.version;
115
+ }
116
+ }
117
+ return found;
118
+ };
119
+ //#endregion
9
120
  //#region src/image.ts
121
+ /** These are the manifests of packages that `versionsOf` reads beside the lockfiles. */
122
+ const READ_FOR_VERSIONS = /* @__PURE__ */ new Set(["package.json", "Cargo.toml"]);
10
123
  /**
11
124
  * This module resolves the source of each image (`ImageSource`) to an image that a lab can start.
12
125
  * A pinned image is its own name. A build context, or an image that the harness ships, becomes a
@@ -117,10 +230,13 @@ const SHIPPED = {
117
230
  /** These are the names of the images that the harness ships. */
118
231
  const SHIPPED_NAMES = Object.keys(SHIPPED);
119
232
  /** This function gives the source of a shipped image. */
120
- const shipped = (name) => ({
233
+ const shippedSource = (name) => ({
121
234
  kind: "shipped",
122
235
  name
123
236
  });
237
+ const shipped = shippedSource;
238
+ /** This function gives the version of the package of the harness, from its package.json. */
239
+ const harnessVersion = () => JSON.parse(readFileSync(join(ROOTS["harness"], "package.json"), "utf8")).version;
124
240
  /**
125
241
  * These are the clients that the harness ships, by their names (`Client`): the image of each, and
126
242
  * its command. A cell that names a client gets this image and this command.
@@ -247,7 +363,12 @@ const subjectProblems = (subject) => {
247
363
  * by its name, or of `scratch`, needs no digest. A base from an `ARG` has no digest that the harness
248
364
  * can read, thus it is in the list too.
249
365
  */
250
- const unpinnedBases = (dockerfile) => {
366
+ const unpinnedBases = (dockerfile) => basesOf(dockerfile).filter((image) => !PINNED.test(image));
367
+ /**
368
+ * This function gives each base of a Dockerfile, in the sequence of the file. A base is the image of
369
+ * a `FROM` that is not `scratch` or an earlier stage by its name.
370
+ */
371
+ const basesOf = (dockerfile) => {
251
372
  const stages = /* @__PURE__ */ new Set(["scratch"]);
252
373
  const found = [];
253
374
  for (const line of dockerfile.replace(/\\\r?\n/g, " ").split(/\r?\n/)) {
@@ -255,7 +376,7 @@ const unpinnedBases = (dockerfile) => {
255
376
  if (from === void 0) continue;
256
377
  const [image, as, stage] = from.split(/\s+/).filter((word) => !word.startsWith("--"));
257
378
  if (image === void 0) continue;
258
- if (!stages.has(image.toLowerCase()) && !PINNED.test(image)) found.push(image);
379
+ if (!stages.has(image.toLowerCase())) found.push(image);
259
380
  if (as?.toLowerCase() === "as" && stage !== void 0) stages.add(stage.toLowerCase());
260
381
  }
261
382
  return found;
@@ -349,20 +470,34 @@ const planOf = (source) => {
349
470
  })));
350
471
  const docker = files.find((each) => each.at === dockerfile);
351
472
  if (docker === void 0) throw new Error(`${labelOf(source)}: the context has no ${dockerfile}`);
352
- const unpinned = unpinnedBases(readFileSync(docker.file, "utf8"));
473
+ const bases = basesOf(readFileSync(docker.file, "utf8"));
474
+ const unpinned = bases.filter((image) => !PINNED.test(image));
353
475
  const unlocked = unlockedManifests(files.map((each) => each.at));
354
476
  if (unpinned.length > 0 || unlocked.length > 0) throw new Error(`${labelOf(source)}: ${[...unpinned.map((image) => `the base ${image} is not pinned by digest`), ...unlocked.map((path) => `${path} has no lockfile`)].join(", ")}`);
355
477
  const hash = createHash("sha256");
356
478
  hash.update(dockerfile);
479
+ const read = [];
357
480
  for (const { file, at } of files) {
481
+ const content = readFileSync(file);
358
482
  hash.update(at);
359
- hash.update(readFileSync(file));
483
+ hash.update(content);
484
+ if (LOCK_NAMES.has(posix.basename(at)) || READ_FOR_VERSIONS.has(posix.basename(at))) read.push({
485
+ at,
486
+ content
487
+ });
360
488
  }
489
+ const tag = `${name}:${hash.digest("hex").slice(0, 12)}`;
361
490
  const plan = {
362
491
  kind: "build",
363
- tag: `${name}:${hash.digest("hex").slice(0, 12)}`,
492
+ tag,
364
493
  dockerfile,
365
- files
494
+ files,
495
+ record: {
496
+ tag,
497
+ bases,
498
+ locks: locksOf(read),
499
+ versions: versionsOf(read)
500
+ }
366
501
  };
367
502
  if (source.kind === "shipped") shippedPlans.set(source.name, plan);
368
503
  return plan;
@@ -463,6 +598,14 @@ const imageOf = async (source) => {
463
598
  const plan = planOf(source);
464
599
  return plan.kind === "pull" ? plan.image : buildOnce(plan);
465
600
  };
601
+ /**
602
+ * This function gives the build record of the image of a source, for the manifest of a run. A
603
+ * stock image by its name, or a pinned image, is a pull, and it has no build record.
604
+ */
605
+ const buildRecordOf = (source) => {
606
+ const plan = planOf(source);
607
+ return plan.kind === "build" ? plan.record : void 0;
608
+ };
466
609
  /** This function gives the image of the harness: the prober, the TCP client, the injector and the namespace admin. */
467
610
  const harnessImage = () => imageOf(shipped("harness"));
468
611
  /** This function gives the image of the Node client. */
@@ -491,6 +634,6 @@ const targetSourceOf = (target) => target.subject ?? target.image;
491
634
  /** This function gives the image that a target operates: the image of its subject, or its stock image. */
492
635
  const targetImageOf = (target) => imageOf(targetSourceOf(target));
493
636
  //#endregion
494
- export { targetImageOf as C, unpinnedBases as E, subjectProblems as S, unlockedManifests as T, pgClientImage as _, SHIPPED_NAMES as a, routerImage as b, grpcClientImage as c, imageOf as d, imageProblems as f, nodeHttpImage as g, nodeClientImage as h, SHIPPED_CLIENTS as i, harnessImage as l, mongoClientImage as m, PINNED as n, amqpClientImage as o, inContext as p, REPOSITORY as r, goHttpImage as s, CLIENT_NAMES as t, hikariClientImage as u, planOf as v, targetSourceOf as w, subjectClient as x, programOf as y };
637
+ export { unpinnedBases as A, routerImage as C, targetImageOf as D, subjectProblems as E, locksOf as F, npmVersions as I, versionsOf as L, cargoVersions as M, goVersions as N, targetSourceOf as O, jarVersions as P, programOf as S, subjectClient as T, mongoClientImage as _, SHIPPED_NAMES as a, pgClientImage as b, buildRecordOf as c, harnessImage as d, harnessVersion as f, inContext as g, imageProblems as h, SHIPPED_CLIENTS as i, LOCK_NAMES as j, unlockedManifests as k, goHttpImage as l, imageOf as m, PINNED as n, amqpClientImage as o, hikariClientImage as p, REPOSITORY as r, basesOf as s, CLIENT_NAMES as t, grpcClientImage as u, nodeClientImage as v, shippedSource as w, planOf as x, nodeHttpImage as y };
495
638
 
496
- //# sourceMappingURL=image-Lg8LL6C3.js.map
639
+ //# sourceMappingURL=image-BMdV7yWD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-BMdV7yWD.js","names":[],"sources":["../src/locks.ts","../src/image.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\n\n/**\n * This module reads the lockfiles of a build context for the manifest of a run. A lockfile pins each\n * package that a build installs, thus its sha256 pins the packages that the image holds. The\n * versions of the packages that each manifest names directly are the part that a person reads.\n */\n\n/** A context file is a file of a build context: its path in the context, and its content. */\nexport interface ContextFile {\n readonly at: string;\n readonly content: Buffer | string;\n}\n\n/**\n * These are the names of the lockfiles that the harness records. A `go.mod` is the lockfile of a Go\n * module, because its `require` lines give exact versions. For them, Go selects the same packages\n * each time, and its checksum database examines each package.\n */\nexport const LOCK_NAMES: ReadonlySet<string> = new Set([\n \"package-lock.json\",\n \"npm-lock.json\",\n \"npm-shrinkwrap.json\",\n \"pnpm-lock.yaml\",\n \"yarn.lock\",\n \"bun.lock\",\n \"Cargo.lock\",\n \"go.mod\",\n \"go.sum\",\n \"jars.sha256\",\n]);\n\nconst nameOf = (at: string): string => at.split(\"/\").at(-1)!;\nconst folderOf = (at: string): string => at.split(\"/\").slice(0, -1).join(\"/\");\nconst textOf = (content: Buffer | string): string => (typeof content === \"string\" ? content : content.toString(\"utf8\"));\n\n/** This function gives each lockfile of a context by its path, with the sha256 of its content in hex. */\nexport const locksOf = (files: readonly ContextFile[]): Record<string, string> =>\n Object.fromEntries(\n files\n .filter((file) => LOCK_NAMES.has(nameOf(file.at)))\n .map((file) => [file.at, createHash(\"sha256\").update(file.content).digest(\"hex\")] as const)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),\n );\n\n/** This function gives the version of each direct dependency of an npm lockfile (lockfileVersion 2 or 3). */\nexport const npmVersions = (lock: string): Record<string, string> => {\n const parsed = JSON.parse(lock) as { packages?: Record<string, { version?: string; dependencies?: Record<string, string>; optionalDependencies?: Record<string, string> }> };\n const packages = parsed.packages ?? {};\n const root = packages[\"\"] ?? {};\n const names = [...Object.keys(root.dependencies ?? {}), ...Object.keys(root.optionalDependencies ?? {})];\n const found: Record<string, string> = {};\n for (const name of names) {\n const version = packages[`node_modules/${name}`]?.version;\n if (version !== undefined) found[name] = version;\n }\n return found;\n};\n\n/** This function gives the packages of a Cargo.lock: the name, the version and the dependencies of each. */\nconst cargoPackages = (lock: string): Array<{ name: string; version: string; dependencies: string[] }> =>\n lock\n .split(/^\\[\\[package\\]\\]\\s*$/m)\n .slice(1)\n .flatMap((block) => {\n const name = /^name = \"([^\"]+)\"$/m.exec(block)?.[1];\n const version = /^version = \"([^\"]+)\"$/m.exec(block)?.[1];\n if (name === undefined || version === undefined) return [];\n const list = /^dependencies = \\[([^\\]]*)\\]/m.exec(block)?.[1] ?? \"\";\n const dependencies = [...list.matchAll(/\"([^\"]+)\"/g)].map((match) => match[1]!);\n return [{ name, version, dependencies }];\n });\n\n/**\n * This function gives the version of each direct dependency of the root package of a Cargo.lock: the\n * package that the Cargo.toml beside it names. An entry of a dependency list is a name, or a name\n * and a version when the lock holds two versions of that name.\n */\nexport const cargoVersions = (lock: string, manifest: string): Record<string, string> => {\n const rootName = /^\\[package\\][^[]*?^name\\s*=\\s*\"([^\"]+)\"/ms.exec(manifest)?.[1];\n const packages = cargoPackages(lock);\n const root = packages.find((each) => each.name === rootName);\n if (root === undefined) return {};\n const found: Record<string, string> = {};\n for (const entry of root.dependencies) {\n const [name, version] = entry.split(\" \");\n const pinned = version ?? packages.find((each) => each.name === name)?.version;\n if (name !== undefined && pinned !== undefined) found[name] = pinned;\n }\n return found;\n};\n\n/** This function gives the version of each direct requirement of a go.mod. A requirement that is `// indirect` is not direct. */\nexport const goVersions = (mod: string): Record<string, string> => {\n const found: Record<string, string> = {};\n const lines = mod.replace(/^require\\s*\\(([^)]*)\\)/gm, (_all, block: string) =>\n block\n .split(/\\r?\\n/)\n .map((line) => `require ${line.trim()}`)\n .join(\"\\n\"),\n );\n for (const line of lines.split(/\\r?\\n/)) {\n const match = /^require\\s+(\\S+)\\s+(v\\S+)(.*)$/.exec(line.trim());\n if (match === null || /\\/\\/\\s*indirect/.test(match[3]!)) continue;\n found[match[1]!] = match[2]!;\n }\n return found;\n};\n\n/** This function gives the version of each jar of a list of jars by their SHA-256: `<hex> HikariCP-7.1.0.jar` gives HikariCP 7.1.0. */\nexport const jarVersions = (list: string): Record<string, string> => {\n const found: Record<string, string> = {};\n for (const line of list.split(/\\r?\\n/)) {\n const match = /^[0-9a-f]{64}\\s+\\*?(.+?)-(\\d[^-]*(?:-[A-Za-z0-9.]+)?)\\.jar$/.exec(line.trim());\n if (match !== null) found[match[1]!] = match[2]!;\n }\n return found;\n};\n\n/**\n * This function gives the version of each package that the manifests of a context name directly,\n * by its name. It reads each npm lockfile, Cargo.lock, go.mod and list of jars. A package of a\n * workspace in the context is a package of the image too, with the version of its package.json.\n * Such a package has a name and a version, and it is not private. The record has the sha256 of\n * the lockfile of a pnpm, yarn or bun workspace (`locksOf`). This function does not read versions\n * from such a lockfile.\n */\nexport const versionsOf = (files: readonly ContextFile[]): Record<string, string> => {\n const byPath = new Map(files.map((file) => [file.at, textOf(file.content)] as const));\n const found: Record<string, string> = {};\n for (const [at, text] of [...byPath].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {\n const name = nameOf(at);\n const folder = folderOf(at);\n const beside = (other: string): string | undefined => byPath.get(folder === \"\" ? other : `${folder}/${other}`);\n if (name === \"package-lock.json\" || name === \"npm-lock.json\" || name === \"npm-shrinkwrap.json\") Object.assign(found, npmVersions(text));\n else if (name === \"Cargo.lock\") Object.assign(found, cargoVersions(text, beside(\"Cargo.toml\") ?? \"\"));\n else if (name === \"go.mod\") Object.assign(found, goVersions(text));\n else if (name === \"jars.sha256\") Object.assign(found, jarVersions(text));\n else if (name === \"package.json\") {\n const manifest = JSON.parse(text) as { name?: string; version?: string; private?: boolean };\n if (manifest.private !== true && manifest.name !== undefined && manifest.version !== undefined) found[manifest.name] = manifest.version;\n }\n }\n return found;\n};\n","import { execFile } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { copyFileSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join, posix, relative, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { isDeepStrictEqual } from \"node:util\";\nimport type { BuildImage, Client, ClientImage, ImageSource, Shipped, ShippedImage, Subject, Target } from \"./cell.ts\";\nimport { LOCK_NAMES, locksOf, versionsOf, type ContextFile } from \"./locks.ts\";\n\n/** These are the manifests of packages that `versionsOf` reads beside the lockfiles. */\nconst READ_FOR_VERSIONS: ReadonlySet<string> = new Set([\"package.json\", \"Cargo.toml\"]);\n\n/**\n * This module resolves the source of each image (`ImageSource`) to an image that a lab can start.\n * A pinned image is its own name. A build context, or an image that the harness ships, becomes a\n * build with a tag from its content. Each image of the harness is a shipped source in one table.\n * Thus the image of a subject is data, as the image of a caller is.\n */\n\n/**\n * These are the roots of the two packages that the shipped images read: this package, and the\n * prober that Node resolves for it. In a workspace, they are two folders side by side. From npm,\n * the prober is a link, and a Docker build context does not follow a link. Thus each image builds\n * from a context that the harness copies from the two roots.\n */\nconst ROOTS: Readonly<Record<string, string>> = {\n harness: fileURLToPath(new URL(\"..\", import.meta.url)),\n prober: dirname(realpathSync(createRequire(import.meta.url).resolve(\"@mark1russell7/prober/package.json\"))),\n};\n\n/** This function gives the path of a part of a shipped image, which is `<package>/<path>` in the context. */\nconst pathOf = (part: string): string => {\n const [root, ...rest] = part.split(\"/\");\n const base = ROOTS[root!];\n if (base === undefined) throw new Error(`${part} is in no package that the images read`);\n return join(base, ...rest);\n};\n\n/** A shipped context is the build context of a shipped image: its Dockerfile, and each file or folder that the Dockerfile reads, as `<package>/<path>`. */\ninterface ShippedContext {\n readonly name: string;\n readonly dockerfile: string;\n readonly from: readonly string[];\n}\n\n/** These are the build contexts of the images that the harness ships, by their names. */\nconst SHIPPED: Readonly<Record<Shipped, ShippedContext>> = {\n // the prober, the TCP client, the injector and the namespace admin\n harness: {\n name: \"loom-harness\",\n dockerfile: \"harness/image/Dockerfile\",\n from: [\"harness/image/Dockerfile\", \"harness/image/native\", \"prober/bpf\", \"prober/native\", \"prober/fixtures\"],\n },\n // the Node client: ioredis 6.0.0 on Node 24.21.0\n node: { name: \"loom-harness-node\", dockerfile: \"harness/image/node/Dockerfile\", from: [\"harness/image/node\"] },\n // the Go HTTP client: the http.Transport of Go 1.27.2\n \"go-http\": { name: \"loom-harness-go-http\", dockerfile: \"harness/image/go-http/Dockerfile\", from: [\"harness/image/go-http\"] },\n // the Node HTTP clients: the http.Agent of Node 24.21.0, and undici 8.12.2\n \"node-http\": { name: \"loom-harness-node-http\", dockerfile: \"harness/image/node-http/Dockerfile\", from: [\"harness/image/node-http\"] },\n // the MongoDB client: the driver mongodb 7.7.0 on Node 24.21.0, and its step-down script\n mongo: { name: \"loom-harness-mongo\", dockerfile: \"harness/image/mongo/Dockerfile\", from: [\"harness/image/mongo\"] },\n // the PostgreSQL client: node-postgres (pg 8.23.1 with pg-pool 3.14.0) on Node 24.21.0\n pg: { name: \"loom-harness-pg\", dockerfile: \"harness/image/pg/Dockerfile\", from: [\"harness/image/pg\"] },\n // the client of HikariCP 7.1.0 over pgjdbc 42.7.13 on Temurin 21.0.12.1: its jars by their SHA-256\n hikari: { name: \"loom-harness-hikari\", dockerfile: \"harness/image/hikari/Dockerfile\", from: [\"harness/image/hikari\"] },\n // the RabbitMQ client: amqplib 2.2.0 on Node 24.21.0\n amqp: { name: \"loom-harness-amqp\", dockerfile: \"harness/image/amqp/Dockerfile\", from: [\"harness/image/amqp\"] },\n // the gRPC client: grpc-js 1.14.6 on Node 24.21.0\n grpc: { name: \"loom-harness-grpc\", dockerfile: \"harness/image/grpc/Dockerfile\", from: [\"harness/image/grpc\"] },\n // the gRPC server: a server of grpc-go v1.84.0, built with Go 1.27.2, that can hold a reply\n \"grpc-go\": { name: \"loom-harness-grpc-go\", dockerfile: \"harness/image/grpc-go/Dockerfile\", from: [\"harness/image/grpc-go\"] },\n // etcd v3.6.15 from its image, on Alpine: a shell to hold it, and its binaries unchanged\n etcd: { name: \"loom-harness-etcd\", dockerfile: \"harness/image/etcd/Dockerfile\", from: [\"harness/image/etcd\"] },\n // the router of a middlebox chain: NAT and conntrack, with a form for a forgotten flow\n router: { name: \"loom-harness-router\", dockerfile: \"harness/image/router/Dockerfile\", from: [\"harness/image/router\"] },\n // PgBouncer 1.26.1, a pooler hop: built from its release tarball, by its SHA-256\n pgbouncer: { name: \"loom-harness-pgbouncer\", dockerfile: \"harness/image/pgbouncer/Dockerfile\", from: [\"harness/image/pgbouncer\"] },\n // Pgpool-II 4.7.3, a pooler hop: built from the source of its tag, by its SHA-256\n pgpool: { name: \"loom-harness-pgpool\", dockerfile: \"harness/image/pgpool/Dockerfile\", from: [\"harness/image/pgpool\"] },\n // docker-proxy of Docker Engine 29.1.5, a proxy hop: its binary from the static bundle of Docker, by its SHA-256\n \"docker-proxy\": { name: \"loom-harness-docker-proxy\", dockerfile: \"harness/image/docker-proxy/Dockerfile\", from: [\"harness/image/docker-proxy\"] },\n};\n\n/** These are the names of the images that the harness ships. */\nexport const SHIPPED_NAMES: readonly [Shipped, ...Shipped[]] = Object.keys(SHIPPED) as [Shipped, ...Shipped[]];\n\n/** This function gives the source of a shipped image. */\nexport const shippedSource = (name: Shipped): ImageSource => ({ kind: \"shipped\", name });\nconst shipped = shippedSource;\n\n/** This function gives the version of the package of the harness, from its package.json. */\nexport const harnessVersion = (): string =>\n (JSON.parse(readFileSync(join(ROOTS[\"harness\"]!, \"package.json\"), \"utf8\")) as { version: string }).version;\n\n/**\n * These are the clients that the harness ships, by their names (`Client`): the image of each, and\n * its command. A cell that names a client gets this image and this command.\n */\nexport const SHIPPED_CLIENTS: Readonly<Record<Client, ClientImage>> = {\n tcp: { image: shipped(\"harness\"), command: [\"/opt/harness/client\"] },\n ioredis: { image: shipped(\"node\"), command: [\"node\", \"/opt/harness/node/client-ioredis.mjs\"] },\n \"go-http\": { image: shipped(\"go-http\"), command: [\"/opt/harness/client-go\"] },\n \"http-agent\": { image: shipped(\"node-http\"), command: [\"node\", \"/opt/harness/node-http/client-agent.mjs\"] },\n undici: { image: shipped(\"node-http\"), command: [\"node\", \"/opt/harness/node-http/client-undici.mjs\"] },\n mongodb: { image: shipped(\"mongo\"), command: [\"node\", \"/opt/harness/mongo/client-mongo.mjs\"] },\n pg: { image: shipped(\"pg\"), command: [\"node\", \"/opt/harness/pg/client-pg.mjs\"] },\n hikari: {\n image: shipped(\"hikari\"),\n command: [\"java\", \"-Dslf4j.internal.verbosity=ERROR\", \"-cp\", \"/opt/harness/hikari/classes:/opt/harness/hikari/lib/*\", \"ClientHikari\"],\n },\n amqp: { image: shipped(\"amqp\"), command: [\"node\", \"/opt/harness/amqp/client-amqp.mjs\"] },\n grpc: { image: shipped(\"grpc\"), command: [\"node\", \"/opt/harness/grpc/client-grpc.mjs\"] },\n};\n\n/** These are the names of the clients that the harness ships. */\nexport const CLIENT_NAMES: readonly [Client, ...Client[]] = Object.keys(SHIPPED_CLIENTS) as [Client, ...Client[]];\n\n/** This function gives the command of a program of a subject by its role. It fails when the subject has no such program. */\nexport const programOf = (subject: Subject, role: string): readonly string[] => {\n const program = Object.hasOwn(subject.programs, role) ? subject.programs[role] : undefined;\n if (program === undefined) throw new Error(`the subject ${subject.name} has no program ${role}`);\n return program;\n};\n\n/**\n * This function gives the client of a subject for a cell (`Cell.client`): the image of the subject,\n * with the command of a program. A client that the harness ships goes by its name, thus its records\n * stay the same.\n */\nexport const subjectClient = (subject: Subject, role = \"client\"): Client | ClientImage => {\n const client: ClientImage = { image: subject.image, command: programOf(subject, role) };\n return CLIENT_NAMES.find((name) => isDeepStrictEqual(SHIPPED_CLIENTS[name], client)) ?? client;\n};\n\n/** This pattern matches an image pinned by digest: a name, a tag or none, then `@sha256:` and 64 hex digits. */\nexport const PINNED: RegExp = /^[a-z0-9][A-Za-z0-9._\\-/:]*@sha256:[0-9a-f]{64}$/;\n/** This pattern matches the repository of a tag that the harness builds. Its parts are lower case, with `/` between them. */\nexport const REPOSITORY: RegExp = /^[a-z0-9]+(?:[._-]+[a-z0-9]+)*(?:\\/[a-z0-9]+(?:[._-]+[a-z0-9]+)*)*$/;\n\n/** This function tells if a path stays in its context: relative, with `/` between its parts, and no `..`. */\nexport const inContext = (path: string): boolean =>\n typeof path === \"string\" &&\n path.length > 0 &&\n !path.startsWith(\"/\") &&\n !path.includes(\"\\\\\") &&\n !/^[A-Za-z]:/.test(path) &&\n !path.split(\"/\").includes(\"..\");\n\n/** This function tells if a value is a command: an array of one text or more. */\nconst isCommand = (value: unknown): boolean =>\n Array.isArray(value) && value.length > 0 && value.every((word) => typeof word === \"string\" && word.length > 0);\n\n/**\n * This function gives the problems of the source of an image, as data. A pinned image names a\n * digest. A build names a repository, a context, and paths that stay in the context. A shipped image\n * is one that the harness ships.\n */\nexport const imageProblems = (source: ImageSource): string[] => {\n const kind = (source as { readonly kind?: unknown } | null)?.kind;\n if (kind === \"pinned\") {\n const { image } = source as { readonly image?: unknown };\n return typeof image === \"string\" && PINNED.test(image) ? [] : [`${String(image)} is not pinned by digest: name@sha256:<64 hex digits>`];\n }\n if (kind === \"shipped\") {\n const { name } = source as { readonly name?: unknown };\n return typeof name === \"string\" && Object.hasOwn(SHIPPED, name) ? [] : [`the harness ships no image ${String(name)}: ${SHIPPED_NAMES.join(\", \")}`];\n }\n if (kind === \"build\") {\n const build = source as Partial<BuildImage>;\n const problems: string[] = [];\n if (typeof build.name !== \"string\" || !REPOSITORY.test(build.name)) problems.push(`${String(build.name)} is not the name of a repository`);\n if (typeof build.context !== \"string\" || build.context.length === 0) problems.push(\"the build names no context\");\n if (build.files !== undefined && (!Array.isArray(build.files) || build.files.length === 0)) problems.push(\"the build names no files\");\n for (const path of [build.dockerfile ?? \"Dockerfile\", ...(Array.isArray(build.files) ? build.files : [])])\n if (!inContext(path)) problems.push(`${String(path)} is not a path in the context`);\n return problems;\n }\n return [`${JSON.stringify(kind)} is not a kind of image source: pinned, build or shipped`];\n};\n\n/**\n * This function gives the problems of a subject, as data. A subject has a name and a base that a\n * digest pins. Its image source has no problems, and each of its programs is a command.\n */\nexport const subjectProblems = (subject: Subject): string[] => {\n const programs = (subject as { readonly programs?: unknown }).programs;\n const roles = typeof programs === \"object\" && programs !== null ? Object.entries(programs) : [];\n return [\n ...(typeof subject.name === \"string\" && subject.name.length > 0 ? [] : [\"the subject has no name\"]),\n ...(typeof subject.base === \"string\" && PINNED.test(subject.base) ? [] : [`the base ${String(subject.base)} is not pinned by digest`]),\n ...imageProblems(subject.image),\n ...(roles.length === 0 ? [\"the subject has no programs\"] : []),\n ...roles.flatMap(([role, command]) => (isCommand(command) ? [] : [`the program ${role} is not a command`])),\n ];\n};\n\n/**\n * This function gives each `FROM` of a Dockerfile that no digest pins. A `FROM` of an earlier stage\n * by its name, or of `scratch`, needs no digest. A base from an `ARG` has no digest that the harness\n * can read, thus it is in the list too.\n */\nexport const unpinnedBases = (dockerfile: string): string[] => basesOf(dockerfile).filter((image) => !PINNED.test(image));\n\n/**\n * This function gives each base of a Dockerfile, in the sequence of the file. A base is the image of\n * a `FROM` that is not `scratch` or an earlier stage by its name.\n */\nexport const basesOf = (dockerfile: string): string[] => {\n const stages = new Set<string>([\"scratch\"]);\n const found: string[] = [];\n for (const line of dockerfile.replace(/\\\\\\r?\\n/g, \" \").split(/\\r?\\n/)) {\n const from = /^\\s*FROM\\s+(.+)$/i.exec(line)?.[1];\n if (from === undefined) continue;\n const [image, as, stage] = from.split(/\\s+/).filter((word) => !word.startsWith(\"--\"));\n if (image === undefined) continue;\n if (!stages.has(image.toLowerCase())) found.push(image);\n if (as?.toLowerCase() === \"as\" && stage !== undefined) stages.add(stage.toLowerCase());\n }\n return found;\n};\n\n/** These are the lockfiles of each manifest of packages. A manifest without a lockfile installs other packages each time. */\nconst LOCKFILES: Readonly<Record<string, readonly string[]>> = {\n \"package.json\": [\"package-lock.json\", \"npm-lock.json\", \"npm-shrinkwrap.json\", \"pnpm-lock.yaml\", \"yarn.lock\", \"bun.lock\"],\n \"Cargo.toml\": [\"Cargo.lock\"],\n};\n\n/**\n * This function gives each manifest of packages in a context (`package.json`, `Cargo.toml`) that\n * has no lockfile in its folder or in a folder above it. The paths are the paths in the context.\n */\nexport const unlockedManifests = (paths: readonly string[]): string[] => {\n const present = new Set(paths);\n return paths.filter((path) => {\n const parts = path.split(\"/\");\n const locks = LOCKFILES[parts.at(-1)!];\n if (locks === undefined) return false;\n for (let depth = parts.length - 1; depth >= 0; depth -= 1) {\n const folder = parts.slice(0, depth).join(\"/\");\n if (locks.some((lock) => present.has(folder === \"\" ? lock : `${folder}/${lock}`))) return false;\n }\n return true;\n });\n};\n\n/**\n * An image plan tells how the harness gets the image of a source. It is a pull of an image by its\n * name, or a build of a context. A build has its tag, its Dockerfile, and each file with its path.\n */\nexport type ImagePlan =\n | { readonly kind: \"pull\"; readonly image: string }\n | {\n readonly kind: \"build\";\n readonly tag: string;\n readonly dockerfile: string;\n readonly files: ReadonlyArray<{ readonly file: string; readonly at: string }>;\n /** This record tells what the image holds, for the manifest of a run. */\n readonly record: BuildRecord;\n };\n\n/**\n * A build record tells what an image that the harness builds holds, for the manifest of a run. The\n * tag is a sha256 of each file of the context. The bases are pinned by digest, and each lockfile\n * has its sha256. Thus the record pins each program of the image. The versions are the part that a\n * person reads.\n */\nexport interface BuildRecord {\n readonly tag: string;\n /** These are the bases of its Dockerfile, each pinned by digest (`basesOf`). */\n readonly bases: readonly string[];\n /** These are the lockfiles of its context by their path, each with the sha256 of its content (`locksOf`). */\n readonly locks: Readonly<Record<string, string>>;\n /** These are the versions of the packages that its manifests name directly, by their names (`versionsOf`). */\n readonly versions: Readonly<Record<string, string>>;\n}\n\n/** This function gives each file under a path, in a stable sequence, but not the output of a local build. */\nconst sources = (path: string): string[] => {\n if (statSync(path).isFile()) return [path];\n return readdirSync(path, { withFileTypes: true })\n .filter((entry) => entry.name !== \"target\" && entry.name !== \"node_modules\")\n .flatMap((entry) => sources(join(path, entry.name)))\n .sort();\n};\n\n/** A part is a file or a folder of a context: its path on the machine, and its path in the context. */\ninterface Part {\n readonly path: string;\n readonly at: string;\n}\n\n/** This function gives the name, the Dockerfile and the parts of the context of a build or of a shipped image. */\nconst contextOf = (source: BuildImage | ShippedImage): { name: string; dockerfile: string; parts: Part[] } => {\n if (source.kind === \"shipped\") {\n const { name, dockerfile, from } = SHIPPED[source.name];\n return { name, dockerfile, parts: from.map((part) => ({ path: pathOf(part), at: part })) };\n }\n const root = resolve(source.context);\n return {\n name: source.name,\n dockerfile: posix.normalize(source.dockerfile ?? \"Dockerfile\"),\n parts: (source.files ?? [\".\"]).map((file) => ({ path: resolve(root, file), at: file === \".\" ? \"\" : file })),\n };\n};\n\n/** This function gives a text for a source in a message. */\nconst labelOf = (source: ImageSource): string =>\n source.kind === \"pinned\" ? source.image : `the ${source.kind === \"build\" ? \"build\" : \"shipped image\"} ${source.name}`;\n\nconst shippedPlans = new Map<Shipped, ImagePlan>();\n\n/**\n * This function gives the plan of the image of a source. A stock image by its name, or a pinned\n * image, is a pull. A build or a shipped image is a build. Its tag is its name, then 12 digits of a\n * sha256. The sha256 reads the path of the Dockerfile, and the path and the content of each file.\n * Thus a changed program cannot operate as an old one.\n *\n * A build fails before it starts on a problem of its source. It also fails on a `FROM` that no\n * digest pins, and on a manifest of packages without a lockfile.\n */\nexport const planOf = (source: string | ImageSource): ImagePlan => {\n if (typeof source === \"string\") return { kind: \"pull\", image: source };\n const problems = imageProblems(source);\n if (problems.length > 0) throw new Error(`${labelOf(source)}: ${problems.join(\", \")}`);\n if (source.kind === \"pinned\") return { kind: \"pull\", image: source.image };\n const known = source.kind === \"shipped\" ? shippedPlans.get(source.name) : undefined;\n if (known !== undefined) return known;\n const { name, dockerfile, parts } = contextOf(source);\n const files = parts.flatMap((part) =>\n sources(part.path).map((file) => ({ file, at: join(part.at, relative(part.path, file)).replaceAll(\"\\\\\", \"/\") })),\n );\n const docker = files.find((each) => each.at === dockerfile);\n if (docker === undefined) throw new Error(`${labelOf(source)}: the context has no ${dockerfile}`);\n const bases = basesOf(readFileSync(docker.file, \"utf8\"));\n const unpinned = bases.filter((image) => !PINNED.test(image));\n const unlocked = unlockedManifests(files.map((each) => each.at));\n if (unpinned.length > 0 || unlocked.length > 0)\n throw new Error(\n `${labelOf(source)}: ${[\n ...unpinned.map((image) => `the base ${image} is not pinned by digest`),\n ...unlocked.map((path) => `${path} has no lockfile`),\n ].join(\", \")}`,\n );\n const hash = createHash(\"sha256\");\n hash.update(dockerfile);\n const read: ContextFile[] = [];\n for (const { file, at } of files) {\n const content = readFileSync(file);\n hash.update(at);\n hash.update(content);\n if (LOCK_NAMES.has(posix.basename(at)) || READ_FOR_VERSIONS.has(posix.basename(at))) read.push({ at, content });\n }\n const tag = `${name}:${hash.digest(\"hex\").slice(0, 12)}`;\n const record: BuildRecord = { tag, bases, locks: locksOf(read), versions: versionsOf(read) };\n const plan: ImagePlan = { kind: \"build\", tag, dockerfile, files, record };\n if (source.kind === \"shipped\") shippedPlans.set(source.name, plan);\n return plan;\n};\n\n/** These counts are how many lines of the output of a failed build its error holds: its error lines, then its last lines. */\nconst BUILD_ERRORS = 10;\nconst BUILD_TAIL = 15;\n\n/**\n * This function gives the lines of the output of a failed build that its error holds. They are each\n * line that names an error, for example the diagnostic of a compiler, then the last lines.\n */\nexport const buildFailure = (output: string): string => {\n const lines = output.trim().split(/\\r?\\n/);\n const tail = lines.slice(-BUILD_TAIL);\n const errors = lines.slice(0, -BUILD_TAIL).filter((line) => /\\berror\\b/i.test(line)).slice(0, BUILD_ERRORS);\n return [...errors, ...(errors.length > 0 ? [\"...\"] : []), ...tail].join(\"\\n\");\n};\n\n/**\n * This function builds a staged context with the CLI of Docker, thus with BuildKit where the CLI has\n * it. Testcontainers reads nothing of the output of a build, and it fails with \"Failed to build\n * image\" only. The error of a failed build here holds its error lines and the end of its output.\n */\nconst dockerBuild = (context: string, plan: Extract<ImagePlan, { readonly kind: \"build\" }>): Promise<void> =>\n new Promise((resolve, reject) => {\n const args = [\"build\", \"--progress=plain\", \"--tag\", plan.tag, \"--file\", join(context, plan.dockerfile), context];\n execFile(\"docker\", args, { maxBuffer: 64 * 1024 * 1024 }, (error, _stdout, stderr) => {\n if (error === null) {\n resolve();\n return;\n }\n reject(new Error(`docker build of ${plan.tag} failed (${error.message.split(\"\\n\")[0]}):\\n${buildFailure(String(stderr))}`));\n });\n });\n\nconst built = new Map<string, Promise<string>>();\n\n/**\n * These are the waits before each next try of a build that fails, in milliseconds. A fault of a\n * registry or of the network often stops in seconds. In run 39278390448 of the cells, two runners\n * failed a build in the same second, and the same builds then passed.\n */\nexport const BUILD_WAITS: readonly number[] = [5_000, 20_000];\n\n/**\n * This function builds the image of a plan one time for each process, from a new context that it\n * copies the files into. Docker keeps its layers between runs. A build that fails tries again after\n * each wait (`waits`), and it writes each failure to stderr. A build that fails each try leaves the\n * cache. Thus a later cell builds again after a fault of Docker or of the network.\n */\nexport const buildOnce = (\n plan: Extract<ImagePlan, { readonly kind: \"build\" }>,\n builder: (context: string, plan: Extract<ImagePlan, { readonly kind: \"build\" }>) => Promise<void> = dockerBuild,\n waits: readonly number[] = BUILD_WAITS,\n): Promise<string> => {\n let tag = built.get(plan.tag);\n if (tag === undefined) {\n const building = (async () => {\n const context = mkdtempSync(join(tmpdir(), `${plan.tag.split(\":\")[0]!.replaceAll(\"/\", \"-\")}-`));\n try {\n for (const { file, at } of plan.files) {\n mkdirSync(dirname(join(context, at)), { recursive: true });\n copyFileSync(file, join(context, at));\n }\n for (let attempt = 0; ; attempt += 1) {\n try {\n await builder(context, plan);\n break;\n } catch (error) {\n const wait = waits[attempt];\n if (wait === undefined) throw error;\n process.stderr.write(`the build of ${plan.tag} failed (${error instanceof Error ? error.message : String(error)}): try ${attempt + 2} after ${wait} ms\\n`);\n await new Promise((resolve) => setTimeout(resolve, wait));\n }\n }\n } finally {\n rmSync(context, { recursive: true, force: true });\n }\n return plan.tag;\n })();\n // a failed build does not stay: the next caller tries again\n building.catch(() => {\n if (built.get(plan.tag) === building) built.delete(plan.tag);\n });\n built.set(plan.tag, building);\n tag = building;\n }\n return tag;\n};\n\n/**\n * This function gives an image that a lab can start, from its source. It is the name of a stock\n * image or of a pinned image, which the lab pulls, or the tag of a build. A build goes through the CLI\n * of Docker.\n */\nexport const imageOf = async (source: string | ImageSource): Promise<string> => {\n const plan = planOf(source);\n return plan.kind === \"pull\" ? plan.image : buildOnce(plan);\n};\n\n/**\n * This function gives the build record of the image of a source, for the manifest of a run. A\n * stock image by its name, or a pinned image, is a pull, and it has no build record.\n */\nexport const buildRecordOf = (source: string | ImageSource): BuildRecord | undefined => {\n const plan = planOf(source);\n return plan.kind === \"build\" ? plan.record : undefined;\n};\n\n/** This function gives the image of the harness: the prober, the TCP client, the injector and the namespace admin. */\nexport const harnessImage = (): Promise<string> => imageOf(shipped(\"harness\"));\n/** This function gives the image of the Node client. */\nexport const nodeClientImage = (): Promise<string> => imageOf(shipped(\"node\"));\n/** This function gives the image of the router. */\nexport const routerImage = (): Promise<string> => imageOf(shipped(\"router\"));\n/** This function gives the image of the Go HTTP client. */\nexport const goHttpImage = (): Promise<string> => imageOf(shipped(\"go-http\"));\n/** This function gives the image of the Node HTTP clients. */\nexport const nodeHttpImage = (): Promise<string> => imageOf(shipped(\"node-http\"));\n/** This function gives the image of the MongoDB client. */\nexport const mongoClientImage = (): Promise<string> => imageOf(shipped(\"mongo\"));\n/** This function gives the image of the PostgreSQL client. */\nexport const pgClientImage = (): Promise<string> => imageOf(shipped(\"pg\"));\n/** This function gives the image of the client of HikariCP over pgjdbc. */\nexport const hikariClientImage = (): Promise<string> => imageOf(shipped(\"hikari\"));\n/** This function gives the image of the RabbitMQ client: amqplib, with the client and the admin of a cluster cell. */\nexport const amqpClientImage = (): Promise<string> => imageOf(shipped(\"amqp\"));\n\n/** This function gives the image of the gRPC client of the harness: grpc-js 1.14.6 on Node 24.21.0, with its client. */\nexport const grpcClientImage = (): Promise<string> => imageOf(shipped(\"grpc\"));\n\n/**\n * This function gives the source of the image that a target operates. It is the image of its\n * subject (`Target.subject`), or its stock image.\n */\nexport const targetSourceOf = (target: Target): string | ImageSource => target.subject ?? target.image;\n\n/** This function gives the image that a target operates: the image of its subject, or its stock image. */\nexport const targetImageOf = (target: Target): Promise<string> => imageOf(targetSourceOf(target));\n"],"mappings":"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"}