@mark1russell7/kind 0.1.8 → 0.2.1

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
@@ -54,7 +54,7 @@ The unit of the main value of a run comes from the prediction of its cell:
54
54
 
55
55
  These values stay numbers in the two formats:
56
56
 
57
- - **A stamp** is a reading of CLOCK_MONOTONIC in milliseconds, a point on a clock, not an amount. The stamps are `start`, `end` and `acts` of a run, `t` of each line, and `first`, `last` and `deletion` of the facts.
57
+ - **A stamp** is a reading of CLOCK_MONOTONIC in milliseconds, a point on a clock, not an amount. The stamps are `start`, `end` and `acts` of a run, `t` of each line, and `first`, `last`, `deletion` and `deletions` of the facts.
58
58
  - **A line** stays as its source wrote it. The image writes it, and the record keeps it.
59
59
  - **A count** or **a share**: the replicas, the thresholds, the runs and the counts of a verdict. The jitter of a poll and the idle share of a load sample are shares.
60
60
 
@@ -89,6 +89,20 @@ Each pod of a cluster shares the kernel of the Docker VM. Thus each stamp of the
89
89
 
90
90
  Before each run, the lab deletes the conntrack entries in SYN_SENT to the ClusterIP on each node (`forgetHandshakes`). A note of the run gives each count. Such an entry is a SYN with no answer, and it keeps its NAT for 120 s. A new SYN from the same port goes where the first one went, and it gets no answer either. The clients send requests between the runs too. Thus the restore of one run left such entries, and a client that took the port again in the next run timed out (T07, 2026-10-17).
91
91
 
92
+ ## The stacks
93
+
94
+ From 0.2.0, a cell names its stack (`stack`): its server and its clients. A cell without a stack has the service.
95
+
96
+ - **`service`** is the service of this repository with its declaration, and the HTTP clients. Each request opens a new connection, thus kube-proxy picks a backend for each request.
97
+ - **`grpc`** is the server of grpc-go v1.84.0 from the image of the harness, and the gRPC clients of the lab image. A client holds one channel. Thus kube-proxy picks a backend only when grpc-js connects again: after a GOAWAY of the server, or after the end of the socket.
98
+ - The probes are the gRPC probes of the kubelet on the port of the server.
99
+ - The lines of grpc-go name their pod and stamp in milliseconds (`--name`, `--clock ms`). Its line `stopping` gives the intake and the SIGTERM of a pod (`stoppingOf`): then GracefulStop closes the listener and sends GOAWAY.
100
+ - The clients start after the server is ready. Each failed connect of a channel makes the backoff of grpc-js longer, up to 120 s.
101
+ - The target of a run is the replica that answers the most clients. A channel stays on its replica, and the channels go to the replica that stays after a run. Thus a target by name has no client in most runs.
102
+ - The cell has no declaration (`declaration` is null), thus `describeCell` refuses it. The server grpc-go has no seam for `liveness-restart`, thus its manifest refuses that trigger.
103
+
104
+ A declaration that can be null changes the type `KindCell`. That is a breaking change before 1.0.0, thus the version is 0.2.0.
105
+
92
106
  ## The image
93
107
 
94
108
  The lab image (`image/`) holds the service of a cell on `@mark1russell7/service`, its clients, the agent and the watcher, on Node 24.21.0. Its base is pinned by digest, and `npm-lock.json` pins the packages from npm. `stageLabImage()` stages its context, and `labImage()` tags the image by its content.
@@ -101,6 +115,7 @@ The lab image (`image/`) holds the service of a cell on `@mark1russell7/service`
101
115
  - **The knobs of the listener.** `server_idle_close` is 6 s, the real idle close of Node 24.21: `keepAliveTimeout` of 5 s and its buffer of 1 s. The sweep of idle connections comes each second after the stop of the intake.
102
116
  - **The seams** of the service are a flag file that fails its liveness check, and SIGUSR2. SIGUSR2 blocks its event loop for the time in a file.
103
117
  - **The clients** (`client.mjs`) send requests on new connections to the ClusterIP. Each request gives one line with its outcome: ok, 503, refused, reset, timeout or unreachable. From 0.1.3, each line also gives the local port of its connection (`lp`), a refused one's too. A conntrack entry is per port. Thus the requests on the port of a failure show the connection before it, and the pod that it went to.
118
+ - **The gRPC clients** (`client-grpc.mjs`) hold one channel of grpc-js 1.14.6 to the ClusterIP, and send a call of `grpc.health.v1.Health/Check` each interval on it. Each call gives the line of a request of `client.mjs`, with the pod that answered (the header `x-pod`). A call that fails at once names the errno of the last connect: refused, reset or unreachable. A call of grpc-js has no deadline at its defaults. Thus a call with no answer in the timeout is a timeout, and the client cancels it.
104
119
 
105
120
  ## The triggers
106
121
 
@@ -111,7 +126,7 @@ The lab image (`image/`) holds the service of a cell on `@mark1russell7/service`
111
126
  | `pod-deletion` | `kubectl delete pod` of the first replica |
112
127
  | `rolling-update` | a new value of an environment variable of the Deployment |
113
128
  | `node-drain` | `kubectl drain` of the node of the first replica, then `uncordon` |
114
- | `scale-in` | one replica less, then back |
129
+ | `scale-in` | one replica less, then back. For the stack `grpc`, the first replica first gets a deletion cost of -1, thus its ReplicaSet removes it |
115
130
  | `crash` | SIGKILL of the process of the service, from the node |
116
131
  | `liveness-restart` | the flag file that fails the liveness check of the first replica |
117
132
  | `freeze-process` | SIGSTOP and SIGCONT: of kube-proxy on one node across a deletion (T07), or of Redis (T18) |
@@ -139,6 +154,15 @@ The plan's `event:clock-jump` has no trigger. A step of CLOCK_REALTIME in the VM
139
154
 
140
155
  From 0.1.8, a run can agree with the law but be past its calibrated band (D-EXTEND). A request failed after the window, but before the rules of its node dropped the target. The check of that run tells it (`tail`), and its cell is `extend`. Such a cell does not hold. A request after the drop is still late, thus its run fails the cell.
141
156
 
157
+ From 0.2.1, a window can be one window for each deleted replica (`each`). A rolling update deletes each old replica in turn, and a client of gRPC can see each of these deletions.
158
+
159
+ - The judge makes one window for each replica of the run (`replicas`) with a deletion after the start of the run. Each window counts from the intake and the deletion of its own replica.
160
+ - A request is late only when it is outside each of these windows. A request after a window and before the rules of its node dropped the replica of that window is a `tail`.
161
+ - The facts of each run give these replicas, with the stamps of their intake and their deletion (`deletions`).
162
+ - `predictionText` adds `of each deleted replica`. A window without `each` stays the window of the target.
163
+
164
+ A preStop sleep that is longer than the grace is a command in the container (`sh -c "sleep 60"`). The API server refuses a sleep action that is longer than the grace.
165
+
142
166
  ## Tests
143
167
 
144
168
  `pnpm --filter @mark1russell7/kind test` starts the tests of the configuration, the tools, the parser of the rules and the fault mapping. It also starts the tests of the predictions, the verdict and the judge of the real records. They do not need Docker.
package/build/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as RulesLine, $t as cidrOverlap, A as TriggerV1, At as secondsOf, B as RunFacts, Bt as Reference, C as KindCellResult, Ct as HEALTH_PORT, D as PredictionV1, Dt as SERVER, E as LoadSample, Et as REDIS, F as KindDecision, Ft as KindRun, G as referenceOf, Gt as TriggerKind, H as factsOf, Ht as ResetReuse, I as KindVerdict, It as KubeletProbe, J as Endpoint, Jt as KindConfig, K as reusesReset, Kt as Workload, L as LAST_SLACK, Lt as PodSettings, M as upgrade, Mt as Band, N as upgradeCell, Nt as Declaration, O as RunCheckV1, Ot as bandOf, P as KIND_NOISE, Pt as KindCell, Q as Request, Qt as NodeImage, R as Propagated, Rt as Prediction, S as KindCellRecordV1, St as CLIENT, T as KindVerdictV1, Tt as PORT, U as judgeKind, Ut as Template, V as checkRun, Vt as Replica, W as propagationOf, Wt as Trigger, X as PodLine, Xt as KubeProxyMode, Y as Line, Yt as KindScenario, Z as RecordLine, Zt as NODE_IMAGES, _ as ClusterManifest, _t as signalsOf, a as STAGES, an as scenario, at as emptyOf, b as FORMAT_V1, bt as slicesOf, c as readRecord, ct as kernelCounts, d as calibrationOf, dt as podsOf, en as cidrRange, et as SliceLine, f as cellsTable, ft as recordsOf, g as BandV1, gt as signalOf, h as timeSpread, ht as rulesOf, i as quantileOf, in as nodeNames, it as dropOf, j as judge, jt as showBand, k as RunFactsV1, kt as seconds, l as Judged, lt as outcomesOf, m as predictionText, mt as restartsAt, n as medianInterval, nn as kindConfig, nt as byNode, o as kindCellRecordSchema, on as scenarioProblems, ot as failuresOf, p as medianText, pt as requestsOf, q as unitOf, qt as KUBE_PROXY_MODES, r as middle, rn as kubeProxyPatch, rt as deletionOf, s as kindCellRecordV1Schema, st as intakeOf, t as RELATIVE, tn as goDuration, tt as backendsAt, u as TimeSpread, ut as podsAt, v as EnvironmentManifest, vt as sliceDropOf, w as KindCellV1, wt as LOG_LABEL, x as KindCellRecord, xt as transitionOf, y as FORMAT, yt as sliceEmptyOf, z as RunCheck, zt as RedisDeclaration } from "./judge-a7yCzwZc.js";
1
+ import { $ as RecordLine, $t as KindConfig, A as TriggerV1, At as REDIS, B as RunCheck, Bt as KubeletProbe, C as KindCellResult, Ct as slicesOf, D as PredictionV1, Dt as HEALTH_PORT, E as LoadSample, Et as CLIENT, F as KIND_NOISE, Ft as showBand, G as judgeKind, Gt as Replica, H as checkRun, Ht as Prediction, I as KindDecision, It as Band, J as reusesReset, Jt as Template, K as propagationOf, Kt as ResetReuse, L as KindVerdict, Lt as Declaration, M as upgrade, Mt as bandOf, N as upgradeCell, Nt as seconds, O as RunCheckV1, Ot as LOG_LABEL, P as Deleted, Pt as secondsOf, Q as PodLine, Qt as KUBE_PROXY_MODES, R as LAST_SLACK, Rt as KindCell, S as KindCellRecordV1, St as sliceEmptyOf, T as KindVerdictV1, Tt as transitionOf, U as deletionsOf, Ut as RedisDeclaration, V as RunFacts, Vt as PodSettings, W as factsOf, Wt as Reference, X as Endpoint, Xt as TriggerKind, Y as unitOf, Yt as Trigger, Z as Line, Zt as Workload, _ as ClusterManifest, _t as restartsAt, a as STAGES, an as cidrRange, at as byNode, b as FORMAT_V1, bt as signalsOf, c as readRecord, cn as kubeProxyPatch, ct as emptyOf, d as calibrationOf, dn as scenarioProblems, dt as kernelCounts, en as KindScenario, et as Request, f as cellsTable, ft as outcomesOf, g as BandV1, gt as requestsOf, h as timeSpread, ht as recordsOf, i as quantileOf, in as cidrOverlap, it as byClients, j as judge, jt as SERVER, k as RunFactsV1, kt as PORT, l as Judged, ln as nodeNames, lt as failuresOf, m as predictionText, mt as podsOf, n as medianInterval, nn as NODE_IMAGES, nt as SliceLine, o as kindCellRecordSchema, on as goDuration, ot as deletionOf, p as medianText, pt as podsAt, q as referenceOf, qt as Stack, r as middle, rn as NodeImage, rt as backendsAt, s as kindCellRecordV1Schema, sn as kindConfig, st as dropOf, t as RELATIVE, tn as KubeProxyMode, tt as RulesLine, u as TimeSpread, un as scenario, ut as intakeOf, v as EnvironmentManifest, vt as rulesOf, w as KindCellV1, wt as stoppingOf, x as KindCellRecord, xt as sliceDropOf, y as FORMAT, yt as signalOf, z as Propagated, zt as KindRun } from "./judge-Dwa6MqP4.js";
2
2
  import { Quantity } from "@mark1russell7/quantity";
3
3
  import { CheckFn, Finding, HealthServer, Poll, Rule, Scenario, ServiceBuilder, ServiceDescription, Stage, Timing, TransportImpl, Verdict } from "@mark1russell7/service";
4
4
  import { RedisClient } from "@mark1russell7/service/redis";
@@ -205,17 +205,37 @@ export declare const openKindLab: (cluster: Cluster, image: string) => Promise<K
205
205
  export declare const localPin: (image: string) => Promise<string>;
206
206
  //#endregion
207
207
  //#region src/manifests.d.ts
208
+ /**
209
+ * This module generates the Kubernetes objects of a cell. They are its namespace, its Redis, the
210
+ * server as a Deployment behind a Service, and one client on each node. The server is the service
211
+ * or grpc-go, by the stack of the cell. Each setting of the pod comes from the cell. The rollout
212
+ * strategy and the probe fields state the defaults of Kubernetes as values.
213
+ */
214
+ /** This function gives the stack of a cell: the service, when the cell names no stack. */
215
+ export declare const stackOf: (cell: Pick<KindCell, "stack">) => Stack;
208
216
  /** This image is Redis 7.4, the stock target of the harness, pinned by digest. */
209
217
  export declare const REDIS_IMAGE = "redis:7.4@sha256:c6eabf748fc7a61dbb5a705c78bcf3d6377b1127a97d0ce965c11c44ba46896f";
210
218
  /** This function gives the namespace of a cell: `lab-` and 10 digits of the sha256 of its name. */
211
219
  export declare const namespaceOf: (cell: Pick<KindCell, "name">) => string;
212
- /** This function gives a probe of the pod: an HTTP GET of the health server, with each field of the cell. */
213
- export declare const probeOf: (path: string, probe: KubeletProbe) => Record<string, unknown>;
220
+ /**
221
+ * This function gives a probe of the pod, with each field of the cell. The probe of the service is an
222
+ * HTTP GET of its health server (`path`). The probe of grpc-go (`path` null) is the gRPC probe of the
223
+ * kubelet. It is a Check of the service "" on the port of the server, and the answer is SERVING until
224
+ * GracefulStop.
225
+ */
226
+ export declare const probeOf: (path: string | null, probe: KubeletProbe) => Record<string, unknown>;
214
227
  /** These are the objects of Redis: one replica behind a Service, with no persistence. */
215
228
  export declare const redisObjects: (namespace: string, image: string) => unknown[];
216
- /** These are the objects of the service: a Service of type ClusterIP, and the Deployment with the settings of the cell. */
229
+ /**
230
+ * These are the objects of the server: a Service of type ClusterIP, and the Deployment with the
231
+ * settings of the cell. The image is the lab image for the service, or the image of grpc-go.
232
+ */
217
233
  export declare const serverObjects: (cell: KindCell, namespace: string, image: string, version?: number) => unknown[];
218
- /** These are the objects of the clients: a DaemonSet with one client on each node, the control plane too. */
234
+ /**
235
+ * These are the objects of the clients: a DaemonSet with one client on each node, the control plane
236
+ * too. The client of the service is `client.mjs`, and the client of grpc-go is `client-grpc.mjs`.
237
+ * Each one is in the lab image, and each one writes the same lines.
238
+ */
219
239
  export declare const clientObjects: (cell: KindCell, namespace: string, image: string, target: string) => unknown[];
220
240
  //#endregion
221
241
  //#region src/predict.d.ts
@@ -395,5 +415,5 @@ export declare const toolPath: (tool: Tool, key?: string, cache?: string) => str
395
415
  */
396
416
  export declare const ensureTool: (tool: Tool, key?: string, cache?: string) => Promise<string>;
397
417
  //#endregion
398
- export { type Band, type BandV1, CLIENT, type Cluster, type ClusterManifest, type Credentials, type Declaration, type DeletionWindow, Endpoint, type EnvironmentManifest, FORMAT, FORMAT_V1, type FaultPlan, HEALTH_PORT, type Judged, KIND_NOISE, KUBE_PROXY_MODES, type KindCell, type KindCellRecord, type KindCellRecordV1, type KindCellResult, type KindCellV1, type KindConfig, type KindDecision, type KindLab, type KindRun, type KindScenario, type KindVerdict, type KindVerdictV1, type KubeProxyMode, type KubeletProbe, LAST_SLACK, LOG_LABEL, Line, type LoadSample, NODE_IMAGES, type NodeImage, PORT, type Parts, PodLine, type PodSettings, type Prediction, type PredictionV1, type Propagated, type Propagation, REDIS, RELATIVE, RecordLine, type RedisDeclaration, type Reference, type Replica, Request, type ResetReuse, RulesLine, type RunCheck, type RunCheckV1, type RunFacts, type RunFactsV1, type RunOptions, SERVER, STAGES, type ServiceRule, SliceLine, type Stamp, type Step, type Target, type Template, type TimeSpread, type Tool, type Trigger, type TriggerKind, type TriggerV1, type Verdicts, type Workload, backendsAt, bandOf, byNode, calibrationOf, cellsTable, checkRun, cidrOverlap, cidrRange, deletionOf, dropOf, emptyOf, factsOf, failuresOf, goDuration, intakeOf, judge, judgeKind, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, kindConfig, kubeProxyPatch, medianInterval, medianText, middle, nodeNames, outcomesOf, podsAt, podsOf, predictionText, propagationOf, quantileOf, readRecord, recordsOf, referenceOf, requestsOf, restartsAt, reusesReset, rulesOf, scenario, scenarioProblems, seconds, secondsOf, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, slicesOf, timeSpread, transitionOf, unitOf, upgrade, upgradeCell };
418
+ export { type Band, type BandV1, CLIENT, type Cluster, type ClusterManifest, type Credentials, type Declaration, type Deleted, type DeletionWindow, Endpoint, type EnvironmentManifest, FORMAT, FORMAT_V1, type FaultPlan, HEALTH_PORT, type Judged, KIND_NOISE, KUBE_PROXY_MODES, type KindCell, type KindCellRecord, type KindCellRecordV1, type KindCellResult, type KindCellV1, type KindConfig, type KindDecision, type KindLab, type KindRun, type KindScenario, type KindVerdict, type KindVerdictV1, type KubeProxyMode, type KubeletProbe, LAST_SLACK, LOG_LABEL, Line, type LoadSample, NODE_IMAGES, type NodeImage, PORT, type Parts, PodLine, type PodSettings, type Prediction, type PredictionV1, type Propagated, type Propagation, REDIS, RELATIVE, RecordLine, type RedisDeclaration, type Reference, type Replica, Request, type ResetReuse, RulesLine, type RunCheck, type RunCheckV1, type RunFacts, type RunFactsV1, type RunOptions, SERVER, STAGES, type ServiceRule, SliceLine, type Stack, type Stamp, type Step, type Target, type Template, type TimeSpread, type Tool, type Trigger, type TriggerKind, type TriggerV1, type Verdicts, type Workload, backendsAt, bandOf, byClients, byNode, calibrationOf, cellsTable, checkRun, cidrOverlap, cidrRange, deletionOf, deletionsOf, dropOf, emptyOf, factsOf, failuresOf, goDuration, intakeOf, judge, judgeKind, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, kindConfig, kubeProxyPatch, medianInterval, medianText, middle, nodeNames, outcomesOf, podsAt, podsOf, predictionText, propagationOf, quantileOf, readRecord, recordsOf, referenceOf, requestsOf, restartsAt, reusesReset, rulesOf, scenario, scenarioProblems, seconds, secondsOf, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, slicesOf, stoppingOf, timeSpread, transitionOf, unitOf, upgrade, upgradeCell };
399
419
  //# sourceMappingURL=index.d.ts.map
package/build/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as REDIS, A as deletionOf, B as requestsOf, C as unitOf, D as quantileOf, E as middle, F as kernelCounts, G as sliceDropOf, H as rulesOf, I as outcomesOf, J as transitionOf, K as sliceEmptyOf, L as podsAt, M as emptyOf, N as failuresOf, O as backendsAt, P as intakeOf, Q as PORT, R as podsOf, S as reusesReset, T as medianInterval, U as signalOf, V as restartsAt, W as signalsOf, X as HEALTH_PORT, Y as CLIENT, Z as LOG_LABEL, _ as checkRun, a as calibrationOf, b as propagationOf, c as predictionText, d as FORMAT_V1, et as SERVER, f as judge, g as LAST_SLACK, h as KIND_NOISE, i as readRecord, it as showBand, j as dropOf, k as byNode, l as timeSpread, m as upgradeCell, n as kindCellRecordSchema, nt as seconds, o as cellsTable, p as upgrade, q as slicesOf, r as kindCellRecordV1Schema, rt as secondsOf, s as medianText, t as STAGES, tt as bandOf, u as FORMAT, v as factsOf, w as RELATIVE, x as referenceOf, y as judgeKind, z as recordsOf } from "./judge-BbTAxRSd.js";
1
+ import { $ as HEALTH_PORT, A as byClients, B as podsOf, C as reusesReset, D as middle, E as medianInterval, F as failuresOf, G as signalOf, H as requestsOf, I as intakeOf, J as sliceEmptyOf, K as signalsOf, L as kernelCounts, M as deletionOf, N as dropOf, O as quantileOf, P as emptyOf, Q as CLIENT, R as outcomesOf, S as referenceOf, T as RELATIVE, U as restartsAt, V as recordsOf, W as rulesOf, X as stoppingOf, Y as slicesOf, Z as transitionOf, _ as checkRun, a as calibrationOf, at as seconds, b as judgeKind, c as predictionText, d as FORMAT_V1, et as LOG_LABEL, f as judge, g as LAST_SLACK, h as KIND_NOISE, i as readRecord, it as bandOf, j as byNode, k as backendsAt, l as timeSpread, m as upgradeCell, n as kindCellRecordSchema, nt as REDIS, o as cellsTable, ot as secondsOf, p as upgrade, q as sliceDropOf, r as kindCellRecordV1Schema, rt as SERVER, s as medianText, st as showBand, t as STAGES, tt as PORT, u as FORMAT, v as deletionsOf, w as unitOf, x as propagationOf, y as factsOf, z as podsAt } from "./judge-reWPMVr4.js";
2
2
  import { valueIn } from "@mark1russell7/quantity";
3
3
  import { execFile, execFileSync } from "node:child_process";
4
4
  import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
@@ -9,7 +9,7 @@ import { fileURLToPath } from "node:url";
9
9
  import { failSpan, lint, service } from "@mark1russell7/service";
10
10
  import { createRequire } from "node:module";
11
11
  import { tmpdir } from "node:os";
12
- import { harnessImage } from "@mark1russell7/harness";
12
+ import { harnessImage, imageOf } from "@mark1russell7/harness";
13
13
  import { redisClient } from "@mark1russell7/service/redis";
14
14
  //#region src/backends.ts
15
15
  const hexIp = (hex) => [
@@ -520,6 +520,8 @@ const POD = "{pod}";
520
520
  const NODE = "{node}";
521
521
  /** This value marks the next version of the Deployment, for a rollout. */
522
522
  const VERSION = "{version}";
523
+ /** This annotation of a pod is its deletion cost for its ReplicaSet: a lower cost goes first at a scale-in. */
524
+ const DELETION_COST = "controller.kubernetes.io/pod-deletion-cost";
523
525
  /** This function gives the fault plan of a cell. */
524
526
  const planOf = (cell) => {
525
527
  const ns = ["-n", "{namespace}"];
@@ -577,7 +579,17 @@ const planOf = (cell) => {
577
579
  }]
578
580
  };
579
581
  case "scale-in": return {
580
- inject: [{
582
+ inject: [...cell.stack === "grpc" ? [{
583
+ do: "kubectl",
584
+ args: [
585
+ ...ns,
586
+ "annotate",
587
+ "pod",
588
+ POD,
589
+ `${DELETION_COST}=-1`,
590
+ "--overwrite"
591
+ ]
592
+ }] : [], {
581
593
  do: "kubectl",
582
594
  args: [
583
595
  ...ns,
@@ -800,6 +812,7 @@ const FILES = [
800
812
  ["npm-lock.json", "image/npm-lock.json"],
801
813
  ["app.mjs", "image/app.mjs"],
802
814
  ["client.mjs", "image/client.mjs"],
815
+ ["client-grpc.mjs", "image/client-grpc.mjs"],
803
816
  ["agent.mjs", "image/agent.mjs"],
804
817
  ["watch.mjs", "image/watch.mjs"],
805
818
  ["compose.ts", "src/compose.ts"],
@@ -1220,6 +1233,14 @@ const localPin = async (image) => {
1220
1233
  };
1221
1234
  //#endregion
1222
1235
  //#region src/manifests.ts
1236
+ /**
1237
+ * This module generates the Kubernetes objects of a cell. They are its namespace, its Redis, the
1238
+ * server as a Deployment behind a Service, and one client on each node. The server is the service
1239
+ * or grpc-go, by the stack of the cell. Each setting of the pod comes from the cell. The rollout
1240
+ * strategy and the probe fields state the defaults of Kubernetes as values.
1241
+ */
1242
+ /** This function gives the stack of a cell: the service, when the cell names no stack. */
1243
+ const stackOf = (cell) => cell.stack ?? "service";
1223
1244
  /** This image is Redis 7.4, the stock target of the harness, pinned by digest. */
1224
1245
  const REDIS_IMAGE = "redis:7.4@sha256:c6eabf748fc7a61dbb5a705c78bcf3d6377b1127a97d0ce965c11c44ba46896f";
1225
1246
  const whole = (amount, what) => {
@@ -1229,18 +1250,28 @@ const whole = (amount, what) => {
1229
1250
  };
1230
1251
  /** This function gives the namespace of a cell: `lab-` and 10 digits of the sha256 of its name. */
1231
1252
  const namespaceOf = (cell) => `lab-${createHash("sha256").update(cell.name).digest("hex").slice(0, 10)}`;
1232
- /** This function gives a probe of the pod: an HTTP GET of the health server, with each field of the cell. */
1253
+ /**
1254
+ * This function gives a probe of the pod, with each field of the cell. The probe of the service is an
1255
+ * HTTP GET of its health server (`path`). The probe of grpc-go (`path` null) is the gRPC probe of the
1256
+ * kubelet. It is a Check of the service "" on the port of the server, and the answer is SERVING until
1257
+ * GracefulStop.
1258
+ */
1233
1259
  const probeOf = (path, probe) => ({
1234
- httpGet: {
1260
+ ...path === null ? { grpc: { port: PORT } } : { httpGet: {
1235
1261
  path,
1236
1262
  port: HEALTH_PORT
1237
- },
1263
+ } },
1238
1264
  initialDelaySeconds: 0,
1239
- periodSeconds: whole(probe.period, `the period of ${path}`),
1240
- timeoutSeconds: whole(probe.timeout, `the timeout of ${path}`),
1265
+ periodSeconds: whole(probe.period, `the period of ${path ?? "the gRPC probe"}`),
1266
+ timeoutSeconds: whole(probe.timeout, `the timeout of ${path ?? "the gRPC probe"}`),
1241
1267
  failureThreshold: probe.threshold,
1242
1268
  successThreshold: 1
1243
1269
  });
1270
+ /** This variable is the name of the pod, from the API: the service and grpc-go put it in their lines. */
1271
+ const POD_ENV = {
1272
+ name: "POD_NAME",
1273
+ valueFrom: { fieldRef: { fieldPath: "metadata.name" } }
1274
+ };
1244
1275
  /** This function gives the environment of the app: its config as JSON, and the name of its pod. */
1245
1276
  const appEnv = (cell) => [{
1246
1277
  name: "CONFIG",
@@ -1250,10 +1281,44 @@ const appEnv = (cell) => [{
1250
1281
  healthPort: HEALTH_PORT,
1251
1282
  declaration: cell.declaration
1252
1283
  })
1253
- }, {
1254
- name: "POD_NAME",
1255
- valueFrom: { fieldRef: { fieldPath: "metadata.name" } }
1256
- }];
1284
+ }, POD_ENV];
1285
+ /**
1286
+ * This function gives the container of the server of a cell. The service runs `app.mjs` of the lab
1287
+ * image with its declaration. The server grpc-go runs from the image of the harness. Its lines name
1288
+ * the pod and stamp in milliseconds. Its replies name the pod in the header `x-pod`. Kubernetes
1289
+ * expands `$(POD_NAME)` in the command.
1290
+ */
1291
+ const serverContainer = (cell, version) => {
1292
+ const env = {
1293
+ name: "LAB_VERSION",
1294
+ value: String(version)
1295
+ };
1296
+ if (stackOf(cell) === "service") return {
1297
+ command: ["node", "app.mjs"],
1298
+ env: [...appEnv(cell), env],
1299
+ ports: [{ containerPort: PORT }, { containerPort: HEALTH_PORT }],
1300
+ ...cell.pod.startup === null ? {} : { startupProbe: probeOf("/startup", cell.pod.startup) },
1301
+ readinessProbe: probeOf("/readiness", cell.pod.readiness),
1302
+ livenessProbe: probeOf("/liveness", cell.pod.liveness)
1303
+ };
1304
+ if (cell.trigger.kind === "liveness-restart") throw new Error(`${cell.name}: grpc-go has no seam for liveness-restart`);
1305
+ return {
1306
+ command: [
1307
+ "grpc-go-server",
1308
+ "--port",
1309
+ String(PORT),
1310
+ "--name",
1311
+ "$(POD_NAME)",
1312
+ "--clock",
1313
+ "ms"
1314
+ ],
1315
+ env: [POD_ENV, env],
1316
+ ports: [{ containerPort: PORT }],
1317
+ ...cell.pod.startup === null ? {} : { startupProbe: probeOf(null, cell.pod.startup) },
1318
+ readinessProbe: probeOf(null, cell.pod.readiness),
1319
+ livenessProbe: probeOf(null, cell.pod.liveness)
1320
+ };
1321
+ };
1257
1322
  /** These are the objects of Redis: one replica behind a Service, with no persistence. */
1258
1323
  const redisObjects = (namespace, image) => [{
1259
1324
  apiVersion: "apps/v1",
@@ -1308,9 +1373,29 @@ const redisObjects = (namespace, image) => [{
1308
1373
  }]
1309
1374
  }
1310
1375
  }];
1311
- /** These are the objects of the service: a Service of type ClusterIP, and the Deployment with the settings of the cell. */
1376
+ /**
1377
+ * This function gives the preStop hook of a sleep, or nothing for no sleep. The hook is the native
1378
+ * sleep action of the kubelet. The API server refuses a sleep action that is longer than the grace
1379
+ * (`validateSleepAction`, `pkg/apis/core/validation/validation.go` at v1.37.0, line 3483). Thus a
1380
+ * longer sleep is a command in the container, `sleep` from `/bin/sh`, as public manifests write it.
1381
+ * The kubelet starts that command with no timeout (`pkg/kubelet/lifecycle/handlers.go`, line 81).
1382
+ */
1383
+ const preStopOf = (preStop, grace) => {
1384
+ if (preStop === 0) return {};
1385
+ if (preStop <= grace) return { lifecycle: { preStop: { sleep: { seconds: preStop } } } };
1386
+ return { lifecycle: { preStop: { exec: { command: [
1387
+ "/bin/sh",
1388
+ "-c",
1389
+ `sleep ${preStop}`
1390
+ ] } } } };
1391
+ };
1392
+ /**
1393
+ * These are the objects of the server: a Service of type ClusterIP, and the Deployment with the
1394
+ * settings of the cell. The image is the lab image for the service, or the image of grpc-go.
1395
+ */
1312
1396
  const serverObjects = (cell, namespace, image, version = 1) => {
1313
1397
  const preStop = whole(cell.pod.preStop, "the preStop sleep");
1398
+ const grace = whole(cell.pod.grace, "the grace period");
1314
1399
  return [{
1315
1400
  apiVersion: "v1",
1316
1401
  kind: "Service",
@@ -1321,7 +1406,7 @@ const serverObjects = (cell, namespace, image, version = 1) => {
1321
1406
  spec: {
1322
1407
  selector: { app: "lab" },
1323
1408
  ports: [{
1324
- name: "http",
1409
+ name: stackOf(cell) === "grpc" ? "grpc" : "http",
1325
1410
  port: 80,
1326
1411
  targetPort: PORT
1327
1412
  }]
@@ -1352,7 +1437,7 @@ const serverObjects = (cell, namespace, image, version = 1) => {
1352
1437
  "lab.systems/version": String(version)
1353
1438
  } },
1354
1439
  spec: {
1355
- terminationGracePeriodSeconds: whole(cell.pod.grace, "the grace period"),
1440
+ terminationGracePeriodSeconds: grace,
1356
1441
  enableServiceLinks: true,
1357
1442
  topologySpreadConstraints: [{
1358
1443
  maxSkew: 1,
@@ -1364,23 +1449,19 @@ const serverObjects = (cell, namespace, image, version = 1) => {
1364
1449
  name: "app",
1365
1450
  image,
1366
1451
  imagePullPolicy: "Never",
1367
- command: ["node", "app.mjs"],
1368
- env: [...appEnv(cell), {
1369
- name: "LAB_VERSION",
1370
- value: String(version)
1371
- }],
1372
- ports: [{ containerPort: PORT }, { containerPort: HEALTH_PORT }],
1373
- ...cell.pod.startup === null ? {} : { startupProbe: probeOf("/startup", cell.pod.startup) },
1374
- readinessProbe: probeOf("/readiness", cell.pod.readiness),
1375
- livenessProbe: probeOf("/liveness", cell.pod.liveness),
1376
- ...preStop === 0 ? {} : { lifecycle: { preStop: { sleep: { seconds: preStop } } } }
1452
+ ...serverContainer(cell, version),
1453
+ ...preStopOf(preStop, grace)
1377
1454
  }]
1378
1455
  }
1379
1456
  }
1380
1457
  }
1381
1458
  }];
1382
1459
  };
1383
- /** These are the objects of the clients: a DaemonSet with one client on each node, the control plane too. */
1460
+ /**
1461
+ * These are the objects of the clients: a DaemonSet with one client on each node, the control plane
1462
+ * too. The client of the service is `client.mjs`, and the client of grpc-go is `client-grpc.mjs`.
1463
+ * Each one is in the lab image, and each one writes the same lines.
1464
+ */
1384
1465
  const clientObjects = (cell, namespace, image, target) => [{
1385
1466
  apiVersion: "apps/v1",
1386
1467
  kind: "DaemonSet",
@@ -1407,7 +1488,7 @@ const clientObjects = (cell, namespace, image, target) => [{
1407
1488
  name: CLIENT,
1408
1489
  image,
1409
1490
  imagePullPolicy: "Never",
1410
- command: ["node", "client.mjs"],
1491
+ command: ["node", stackOf(cell) === "grpc" ? "client-grpc.mjs" : "client.mjs"],
1411
1492
  env: [
1412
1493
  {
1413
1494
  name: "TARGET",
@@ -1694,6 +1775,7 @@ const lintCell = (description, cell, propagation) => lint(description, scenarioO
1694
1775
  const verdictsOf = (findings) => Object.fromEntries(findings.map((finding) => [finding.rule, finding.verdict]));
1695
1776
  /** This function gives the description of the service of a cell, as the app of the image composes it. Nothing connects. */
1696
1777
  const describeCell = (cell) => {
1778
+ if (cell.declaration === null) throw new Error(`${cell.name}: the server is not the service, thus the cell has no declaration to describe`);
1697
1779
  const redis = cell.declaration.redis;
1698
1780
  const client = redis === null ? void 0 : redisClient("redis", {
1699
1781
  host: "redis",
@@ -1713,12 +1795,16 @@ const RULES_EVERY = {
1713
1795
  };
1714
1796
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1715
1797
  const slug = (name) => name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1798
+ /** This function tells if a cell has Redis: only a declaration of the service can have it. */
1799
+ const hasRedis = (cell) => cell.declaration !== null && cell.declaration.redis !== null;
1716
1800
  /** This tracker keeps the latest state of the cell from the lines: each pod, each slice, the backends of each node and each client. */
1717
1801
  var Tracker = class {
1718
1802
  pods = /* @__PURE__ */ new Map();
1719
1803
  slices = /* @__PURE__ */ new Map();
1720
1804
  backends = /* @__PURE__ */ new Map();
1721
1805
  clients = /* @__PURE__ */ new Map();
1806
+ /** This map is the backend of the last answer of each client, by client. */
1807
+ answers = /* @__PURE__ */ new Map();
1722
1808
  last = 0;
1723
1809
  see = (line) => {
1724
1810
  if (typeof line.t === "number" && line.t > this.last && line.ev === "req") this.last = line.t;
@@ -1740,6 +1826,7 @@ var Tracker = class {
1740
1826
  if (request.o === "ok") client.ok = Math.max(client.ok, request.t);
1741
1827
  else client.failed = Math.max(client.failed, request.t);
1742
1828
  this.clients.set(request.p, client);
1829
+ if (request.o === "ok" && request.b !== void 0) this.answers.set(request.p, request.b);
1743
1830
  }
1744
1831
  };
1745
1832
  servers() {
@@ -1754,7 +1841,7 @@ const unsteady = (ctx) => {
1754
1841
  const busy = servers.find((pod) => pod.del || !pod.ready);
1755
1842
  if (busy !== void 0) return `${busy.name} is not ready`;
1756
1843
  if (new Set(servers.map((pod) => pod.labels["pod-template-hash"])).size > 1) return "two versions of the replicas";
1757
- if (cell.declaration.redis !== null) {
1844
+ if (hasRedis(cell)) {
1758
1845
  const redis = [...tracker.pods.values()].filter((pod) => pod.labels["app"] === REDIS);
1759
1846
  if (redis.length !== 1 || !redis[0].ready || redis[0].del) return "Redis is not ready";
1760
1847
  }
@@ -1819,16 +1906,20 @@ const quiet = async (ctx, ours) => {
1819
1906
  await sleep(3e4);
1820
1907
  }
1821
1908
  };
1822
- /** This function deploys a cell in its namespace, with the watcher, the poll of the rules and the clients. */
1823
- const deploy = async (lab, cluster, cell, namespace, redisImage) => {
1909
+ /**
1910
+ * This function deploys a cell in its namespace, with the watcher, the poll of the rules and the
1911
+ * clients. A client of gRPC starts after the server is ready: each failed connect of a channel makes
1912
+ * the backoff of grpc-js longer, up to 120 s.
1913
+ */
1914
+ const deploy = async (lab, cluster, cell, namespace, images) => {
1824
1915
  await cluster.apply([{
1825
1916
  apiVersion: "v1",
1826
1917
  kind: "Namespace",
1827
1918
  metadata: { name: namespace }
1828
1919
  }]);
1829
1920
  await lab.watch([namespace]);
1830
- if (cell.declaration.redis !== null) {
1831
- await cluster.apply(redisObjects(namespace, redisImage));
1921
+ if (hasRedis(cell)) {
1922
+ await cluster.apply(redisObjects(namespace, images.redis));
1832
1923
  await cluster.kubectl([
1833
1924
  "-n",
1834
1925
  namespace,
@@ -1838,7 +1929,9 @@ const deploy = async (lab, cluster, cell, namespace, redisImage) => {
1838
1929
  "--timeout=180s"
1839
1930
  ]);
1840
1931
  }
1841
- await cluster.apply(serverObjects(cell, namespace, lab.image));
1932
+ const grpc = stackOf(cell) === "grpc";
1933
+ if (grpc && images.grpc === void 0) throw new Error(`${cell.name}: the cluster has no image of grpc-go`);
1934
+ await cluster.apply(serverObjects(cell, namespace, grpc ? images.grpc : lab.image));
1842
1935
  const clusterIp = (await cluster.kubectl([
1843
1936
  "-n",
1844
1937
  namespace,
@@ -1848,7 +1941,8 @@ const deploy = async (lab, cluster, cell, namespace, redisImage) => {
1848
1941
  "-o",
1849
1942
  "jsonpath={.spec.clusterIP}"
1850
1943
  ])).trim();
1851
- await cluster.apply(clientObjects(cell, namespace, lab.image, `${clusterIp}:80`));
1944
+ const clients = clientObjects(cell, namespace, lab.image, `${clusterIp}:80`);
1945
+ if (!grpc) await cluster.apply(clients);
1852
1946
  await cluster.kubectl([
1853
1947
  "-n",
1854
1948
  namespace,
@@ -1857,6 +1951,7 @@ const deploy = async (lab, cluster, cell, namespace, redisImage) => {
1857
1951
  `deployment/lab`,
1858
1952
  "--timeout=300s"
1859
1953
  ]);
1954
+ if (grpc) await cluster.apply(clients);
1860
1955
  await cluster.kubectl([
1861
1956
  "-n",
1862
1957
  namespace,
@@ -1889,13 +1984,21 @@ const probeThePods = async (ctx) => {
1889
1984
  await ctx.lab.probe([...new Set(netns)]);
1890
1985
  ctx.probed = key;
1891
1986
  };
1892
- /** This function gives the replicas of the service at this time, in the sequence of their names. */
1893
- const replicasOf = (ctx) => ctx.tracker.servers().map((pod) => ({
1894
- pod: pod.name,
1895
- node: pod.node ?? "",
1896
- ip: pod.ip ?? "",
1897
- version: pod.labels["pod-template-hash"] ?? ""
1898
- }));
1987
+ /**
1988
+ * This function gives the replicas of the server at this time, in the sequence of their names. The
1989
+ * first one is the target of a run. A client of gRPC holds its connection to one replica, and the
1990
+ * channels go to the replica that stays after a run. Thus for the stack `grpc`, the first replica
1991
+ * is the one that answers the most clients (`byClients`).
1992
+ */
1993
+ const replicasOf = (ctx) => {
1994
+ const replicas = ctx.tracker.servers().map((pod) => ({
1995
+ pod: pod.name,
1996
+ node: pod.node ?? "",
1997
+ ip: pod.ip ?? "",
1998
+ version: pod.labels["pod-template-hash"] ?? ""
1999
+ }));
2000
+ return stackOf(ctx.cell) === "grpc" ? byClients(replicas, ctx.tracker.answers) : replicas;
2001
+ };
1899
2002
  /** This function does one step of a fault plan. It gives the stamp of its act, or null for an act without a stamp. */
1900
2003
  const doStep = async (ctx, step, replicas, background) => {
1901
2004
  const { lab, cluster, namespace } = ctx;
@@ -2063,7 +2166,7 @@ const runOnce = async (ctx, env, index, warmup, ours) => {
2063
2166
  if (await steady(ctx, 3e5) !== null) return empty("the cell was not at rest after the wait for a quiet VM", await lab.now());
2064
2167
  const replicas = replicasOf(ctx);
2065
2168
  const restarts = new Map(ctx.tracker.servers().map((pod) => [pod.name, pod.cs.reduce((sum, status) => sum + status.rc, 0)]));
2066
- const target = cell.declaration.redis !== null && [
2169
+ const target = hasRedis(cell) && [
2067
2170
  "rst",
2068
2171
  "blackhole",
2069
2172
  "freeze-process",
@@ -2206,6 +2309,15 @@ const runKindCells = async (cells, options) => {
2206
2309
  const environments = options.environments ?? 2;
2207
2310
  const image = await labImage();
2208
2311
  const redisImage = await localPin(REDIS_IMAGE);
2312
+ const grpcImage = cells.some((cell) => stackOf(cell) === "grpc") ? await imageOf({
2313
+ kind: "shipped",
2314
+ name: "grpc-go"
2315
+ }) : void 0;
2316
+ const images = {
2317
+ lab: image,
2318
+ redis: redisImage,
2319
+ grpc: grpcImage
2320
+ };
2209
2321
  const groups = /* @__PURE__ */ new Map();
2210
2322
  for (const cell of cells) groups.set(`${cell.mode}-${cell.workers}`, [...groups.get(`${cell.mode}-${cell.workers}`) ?? [], cell]);
2211
2323
  const results = [];
@@ -2221,6 +2333,7 @@ const runKindCells = async (cells, options) => {
2221
2333
  try {
2222
2334
  await cluster.load(image);
2223
2335
  await cluster.load(redisImage);
2336
+ if (grpcImage !== void 0 && group.some((cell) => stackOf(cell) === "grpc")) await cluster.load(grpcImage);
2224
2337
  lab = await openKindLab(cluster, image);
2225
2338
  const { GenericContainer } = await import("testcontainers");
2226
2339
  const Shared = class extends GenericContainer {
@@ -2239,7 +2352,7 @@ const runKindCells = async (cells, options) => {
2239
2352
  const off = lab.listen(tracker.see);
2240
2353
  const loads = [];
2241
2354
  try {
2242
- const clusterIp = await deploy(lab, cluster, cell, namespace, redisImage);
2355
+ const clusterIp = await deploy(lab, cluster, cell, namespace, images);
2243
2356
  for (const name of [...lab.own(), loader.getName().replace(/^\//, "")]) ours.add(name);
2244
2357
  const ctx = {
2245
2358
  lab,
@@ -2271,7 +2384,8 @@ const runKindCells = async (cells, options) => {
2271
2384
  cluster: manifest,
2272
2385
  images: {
2273
2386
  lab: image,
2274
- redis: redisImage
2387
+ redis: redisImage,
2388
+ ...stackOf(cell) === "grpc" ? { grpc: grpcImage } : {}
2275
2389
  }
2276
2390
  });
2277
2391
  entry.loads.push(...loads);
@@ -2360,6 +2474,6 @@ const shardOf = (cells, index, count) => {
2360
2474
  return out;
2361
2475
  };
2362
2476
  //#endregion
2363
- export { CLIENT, CONTROLLER, DOCUMENTED, DRAIN_RULES, FLAG_CHECK, FORMAT, FORMAT_V1, HEALTH_PORT, KEPT_KERNEL, KIND, KIND_NOISE, KUBECTL, KUBE_PROXY_MODES, LAST_SLACK, LOG_LABEL, NODE_IMAGES, O04, O09, O10, O16, PORT, REDIS, REDIS_IMAGE, RELATIVE, ROUTE, RULES_EVERY, SERVER, STAGES, WATCHDOG, backendsAt, backendsOf, bandOf, byNode, cacheFolder, calibrationOf, capOf, cellsTable, checkRun, cidrOverlap, cidrRange, clientObjects, compose, createCluster, deletes, deletionOf, deletionWindow, describeCell, dropOf, emptyOf, ensureTool, estimatedTime, factsOf, failuresOf, goDuration, intakeAfter, intakeOf, judge, judgeKind, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, kindConfig, kubeProxyPatch, kubeletBand, labImage, latchBand, lintCell, localPin, medianInterval, medianText, middle, namespaceOf, nodeNames, openKindLab, outcomesOf, pausedWindow, planOf, platformKey, podsAt, podsOf, predictionText, probeOf, propagationOf, quantileOf, readRecord, recordsOf, redisObjects, referenceOf, removalBand, requestsOf, restartBand, restartsAt, reusesReset, rulesOf, runKindCells, scenario, scenarioOf, scenarioProblems, seconds, secondsOf, serverObjects, sha256, shardOf, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, slicesOf, stageLabImage, stallRemovalBand, timeSpread, toolPath, transitionOf, unitOf, upgrade, upgradeCell, verdictsOf, writeResult };
2477
+ export { CLIENT, CONTROLLER, DOCUMENTED, DRAIN_RULES, FLAG_CHECK, FORMAT, FORMAT_V1, HEALTH_PORT, KEPT_KERNEL, KIND, KIND_NOISE, KUBECTL, KUBE_PROXY_MODES, LAST_SLACK, LOG_LABEL, NODE_IMAGES, O04, O09, O10, O16, PORT, REDIS, REDIS_IMAGE, RELATIVE, ROUTE, RULES_EVERY, SERVER, STAGES, WATCHDOG, backendsAt, backendsOf, bandOf, byClients, byNode, cacheFolder, calibrationOf, capOf, cellsTable, checkRun, cidrOverlap, cidrRange, clientObjects, compose, createCluster, deletes, deletionOf, deletionWindow, deletionsOf, describeCell, dropOf, emptyOf, ensureTool, estimatedTime, factsOf, failuresOf, goDuration, intakeAfter, intakeOf, judge, judgeKind, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, kindConfig, kubeProxyPatch, kubeletBand, labImage, latchBand, lintCell, localPin, medianInterval, medianText, middle, namespaceOf, nodeNames, openKindLab, outcomesOf, pausedWindow, planOf, platformKey, podsAt, podsOf, predictionText, probeOf, propagationOf, quantileOf, readRecord, recordsOf, redisObjects, referenceOf, removalBand, requestsOf, restartBand, restartsAt, reusesReset, rulesOf, runKindCells, scenario, scenarioOf, scenarioProblems, seconds, secondsOf, serverObjects, sha256, shardOf, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, slicesOf, stackOf, stageLabImage, stallRemovalBand, stoppingOf, timeSpread, toolPath, transitionOf, unitOf, upgrade, upgradeCell, verdictsOf, writeResult };
2364
2478
 
2365
2479
  //# sourceMappingURL=index.js.map