@mark1russell7/kind 0.6.0 → 0.7.0
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 +10 -0
- package/build/index.d.ts +20 -3
- package/build/index.js +86 -5
- package/build/index.js.map +1 -1
- package/build/{judge-B9kVpFE-.d.ts → judge-B1nSdbS0.d.ts} +35 -4
- package/build/{judge-DNCI5vvt.js → judge-CmiF-Fcs.js} +25 -5
- package/build/judge-CmiF-Fcs.js.map +1 -0
- package/build/judge.d.ts +2 -2
- package/build/judge.js +1 -1
- package/package.json +1 -1
- package/build/judge-DNCI5vvt.js.map +0 -1
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["s","seconds","deletesAPod"],"sources":["../src/backends.ts","../src/scenario.ts","../src/tools.ts","../src/cluster.ts","../src/compose.ts","../src/eds.ts","../src/faults.ts","../src/image.ts","../src/istio.ts","../src/lds.ts","../src/conntrack.ts","../src/lab.ts","../src/manifests.ts","../src/predict.ts","../src/rules.ts","../src/run.ts"],"sourcesContent":["// The parser of the rules of kube-proxy. The agent of each node reads the rules with it, and the\n// image copies this file, thus it imports nothing.\n\n/** A service rule names the Service whose backends the agent reads: its namespace, its name, and its ClusterIP and port for IPVS. */\nexport interface ServiceRule {\n readonly mode: \"iptables\" | \"ipvs\" | \"nftables\";\n readonly ns: string;\n readonly svc: string;\n readonly ip: string;\n readonly port: number;\n}\n\nconst hexIp = (hex: string): string =>\n [0, 2, 4, 6].map((at) => parseInt(hex.slice(at, at + 2), 16)).join(\".\");\n\n/**\n * This function gives the backends of a Service in a dump of the rules of kube-proxy, as\n * `ip:port`, sorted. A backend of IPVS with the weight zero takes no new connection, thus it is\n * not a backend.\n * - iptables: the `KUBE-SVC` chain of the Service jumps to a `KUBE-SEP` chain for each backend, and that chain does the DNAT.\n * - nftables: the `service-` chain of the Service does the DNAT to each backend, or it names an `endpoint-` chain for each one.\n * - IPVS: `/proc/net/ip_vs` has the line of the virtual service, then a line for each real server.\n */\nexport const backendsOf = (mode: ServiceRule[\"mode\"], text: string, rule: Pick<ServiceRule, \"ns\" | \"svc\" | \"ip\" | \"port\">): string[] => {\n const out = new Set<string>();\n const lines = text.split(\"\\n\");\n if (mode === \"iptables\") {\n const seps = new Set<string>();\n for (const line of lines) {\n if (!line.startsWith(\"-A KUBE-SVC-\") || !line.includes(`${rule.ns}/${rule.svc}:`)) continue;\n const jump = / -j (KUBE-SEP-\\S+)/.exec(line);\n if (jump !== null) seps.add(jump[1]!);\n }\n for (const line of lines) {\n const chain = /^-A (KUBE-SEP-\\S+) /.exec(line);\n if (chain === null || !seps.has(chain[1]!)) continue;\n const to = / --to-destination (\\S+)/.exec(line);\n if (to !== null) out.add(to[1]!);\n }\n } else if (mode === \"nftables\") {\n // a chain name with a slash can print in quotes: `chain \"service-…\" {` and `goto \"endpoint-…\"`\n let inside = false;\n const ours = `-${rule.ns}/${rule.svc}/`;\n for (const line of lines) {\n const head = /^\\s*chain\\s+\"?([^\"\\s{]+)\"?\\s*\\{/.exec(line);\n if (head !== null) inside = head[1]!.startsWith(\"service-\") && head[1]!.includes(ours);\n else if (/^\\s*(set|map)\\s/.test(line)) inside = false;\n if (!inside) continue;\n // one chain for each endpoint, as kube-proxy 1.31 writes it\n for (const match of line.matchAll(/(?:goto|jump)\\s+\"?endpoint-[0-9A-Za-z]+(-[^\\s,}\"]+)__([0-9.]+)\\/(\\d+)\"?/g))\n if (match[1]!.startsWith(ours)) out.add(`${match[2]}:${match[3]}`);\n // a DNAT in the service chain, as kube-proxy 1.37 writes it: `numgen random mod 2 map { 0 : IP . PORT, … }`, or `dnat to IP:PORT`\n for (const match of line.matchAll(/(?:^|[\\s{,:])(\\d+\\.\\d+\\.\\d+\\.\\d+) \\. (\\d+)(?=\\s*[,}]|\\s*$)/g)) out.add(`${match[1]}:${match[2]}`);\n for (const match of line.matchAll(/\\bdnat (?:ip )?to (\\d+\\.\\d+\\.\\d+\\.\\d+):(\\d+)/g)) out.add(`${match[1]}:${match[2]}`);\n }\n } else {\n let inside = false;\n for (const line of lines) {\n // each virtual service starts a block: a UDP or an SCTP one ends the block of the TCP service before it\n const service = /^(TCP|UDP|SCTP)\\s+([0-9A-F]{8}):([0-9A-F]{4})\\s/.exec(line);\n if (service !== null) {\n inside = service[1] === \"TCP\" && hexIp(service[2]!) === rule.ip && parseInt(service[3]!, 16) === rule.port;\n continue;\n }\n const real = /^\\s+->\\s+([0-9A-F]{8}):([0-9A-F]{4})\\s+\\S+\\s+(\\d+)/.exec(line);\n if (inside && real !== null && Number(real[3]) > 0) out.add(`${hexIp(real[1]!)}:${parseInt(real[2]!, 16)}`);\n }\n }\n return [...out].sort();\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\n\n/** A kube-proxy mode is the dataplane of each node: iptables (the default of kind), IPVS or nftables. */\nexport type KubeProxyMode = \"iptables\" | \"ipvs\" | \"nftables\";\nexport const KUBE_PROXY_MODES: readonly KubeProxyMode[] = [\"iptables\", \"ipvs\", \"nftables\"];\n\n/** A node image is the kind node image of one Kubernetes version, pinned by digest. */\nexport interface NodeImage {\n readonly kubernetes: string;\n readonly image: string;\n}\n\n/**\n * These are the node images of kind v0.33.0, from its release notes. The image of Kubernetes 1.37\n * is v1.37.0. The research names v1.37.1 for some defaults, thus a default that changed in the\n * patch release is a mismatch to record.\n */\nexport const NODE_IMAGES: Readonly<Record<string, NodeImage>> = {\n \"1.37\": {\n kubernetes: \"v1.37.0\",\n image: \"kindest/node:v1.37.0@sha256:a1ed56cfb0e7b93589bdf97c8cd566405a265939e3620fc4f5de89adff580ae5\",\n },\n \"1.36\": {\n kubernetes: \"v1.36.4\",\n image: \"kindest/node:v1.36.4@sha256:099e049362a1526b2db71494e1947aae99bd16290d7c895f2b7ea312e3cbfaed\",\n },\n};\n\n/**\n * A scenario is one kind cluster as data. The lab generates the configuration of kind from it, and\n * nothing comes from a default of kind. The scenario gives the node count, the version, the mode\n * of kube-proxy and its sync periods, the CNI and the subnets.\n */\nexport interface KindScenario {\n /** This name names the cluster and its Docker network: lowercase letters, digits and dashes. */\n readonly name: string;\n /** This key selects the node image (`NODE_IMAGES`). */\n readonly kubernetes: string;\n /** This count is the number of workers. The cluster also has one control plane. */\n readonly workers: number;\n readonly kubeProxy: {\n readonly mode: KubeProxyMode;\n /** This time is the shortest interval between two syncs of the rules. */\n readonly minSyncPeriod: Quantity;\n /** This time is the interval of the full sync of the rules. */\n readonly syncPeriod: Quantity;\n };\n /** This CNI is kindnet, the CNI of kind. Cilium comes later. */\n readonly cni: \"kindnet\";\n readonly podSubnet: string;\n readonly serviceSubnet: string;\n}\n\n/** This function gives a scenario with the defaults of Kubernetes for kube-proxy, as explicit values. */\nexport const scenario = (name: string, mode: KubeProxyMode, workers: number, kubernetes = \"1.37\"): KindScenario => ({\n name,\n kubernetes,\n workers,\n kubeProxy: { mode, minSyncPeriod: { value: 1, unit: \"s\" }, syncPeriod: { value: 30, unit: \"s\" } },\n cni: \"kindnet\",\n podSubnet: \"10.244.0.0/16\",\n serviceSubnet: \"10.96.0.0/16\",\n});\n\nconst NAME = /^[a-z0-9]([a-z0-9-]{0,30}[a-z0-9])?$/;\n\n/** This function gives the problems of a scenario: a name that kind refuses, no image for the version, or a count out of range. */\nexport const scenarioProblems = (s: KindScenario): string[] => {\n const problems: string[] = [];\n if (!NAME.test(s.name)) problems.push(`the name ${s.name} is not lowercase letters, digits and dashes of 1 to 32 characters`);\n if (NODE_IMAGES[s.kubernetes] === undefined) problems.push(`no node image is pinned for Kubernetes ${s.kubernetes}`);\n if (!Number.isInteger(s.workers) || s.workers < 0 || s.workers > 8) problems.push(`${s.workers} workers is not a whole number from 0 to 8`);\n if (cidrOverlap(s.podSubnet, s.serviceSubnet)) problems.push(`the pod subnet ${s.podSubnet} overlaps the service subnet ${s.serviceSubnet}`);\n return problems;\n};\n\n/** This function gives the names of the node containers, as kind names them: the control plane, then each worker. */\nexport const nodeNames = (s: KindScenario): string[] => [\n `${s.name}-control-plane`,\n ...Array.from({ length: s.workers }, (_, at) => `${s.name}-worker${at === 0 ? \"\" : at + 1}`),\n];\n\n/** This function gives a duration as kube-proxy reads it, in whole milliseconds: `1s`, `500ms`. */\nexport const goDuration = (amount: Quantity): string => {\n const ms = valueIn(amount, \"ms\");\n if (!Number.isInteger(ms) || ms < 0) throw new RangeError(`${ms} ms is not a whole number of milliseconds`);\n return ms % 1000 === 0 ? `${ms / 1000}s` : `${ms}ms`;\n};\n\n/** This function gives the KubeProxyConfiguration patch of a scenario, as kubeadm reads it. */\nexport const kubeProxyPatch = (s: KindScenario): string =>\n [\n \"apiVersion: kubeproxy.config.k8s.io/v1alpha1\",\n \"kind: KubeProxyConfiguration\",\n `mode: ${s.kubeProxy.mode}`,\n `${s.kubeProxy.mode}:`,\n ` minSyncPeriod: ${goDuration(s.kubeProxy.minSyncPeriod)}`,\n ` syncPeriod: ${goDuration(s.kubeProxy.syncPeriod)}`,\n \"\",\n ].join(\"\\n\");\n\n/** A kind configuration is the cluster file that `kind create cluster --config` reads. JSON is YAML, thus the lab writes it as JSON. */\nexport interface KindConfig {\n readonly kind: \"Cluster\";\n readonly apiVersion: \"kind.x-k8s.io/v1alpha4\";\n readonly networking: {\n readonly ipFamily: \"ipv4\";\n readonly kubeProxyMode: KubeProxyMode;\n readonly disableDefaultCNI: boolean;\n readonly podSubnet: string;\n readonly serviceSubnet: string;\n };\n readonly nodes: ReadonlyArray<{\n readonly role: \"control-plane\" | \"worker\";\n readonly image: string;\n readonly labels: Readonly<Record<string, string>>;\n }>;\n readonly kubeadmConfigPatches: readonly string[];\n}\n\n/** This function generates the configuration of kind from a scenario. It refuses a scenario with a problem. */\nexport const kindConfig = (s: KindScenario): KindConfig => {\n const problems = scenarioProblems(s);\n if (problems.length > 0) throw new Error(`the scenario ${s.name}: ${problems.join(\"; \")}`);\n const image = NODE_IMAGES[s.kubernetes]!.image;\n return {\n kind: \"Cluster\",\n apiVersion: \"kind.x-k8s.io/v1alpha4\",\n networking: {\n ipFamily: \"ipv4\",\n kubeProxyMode: s.kubeProxy.mode,\n disableDefaultCNI: false,\n podSubnet: s.podSubnet,\n serviceSubnet: s.serviceSubnet,\n },\n nodes: nodeNames(s).map((node, at) => ({\n role: at === 0 ? \"control-plane\" : \"worker\",\n image,\n labels: { \"lab.systems/node\": node.slice(s.name.length + 1) },\n })),\n kubeadmConfigPatches: [kubeProxyPatch(s)],\n };\n};\n\n/** This function gives the first and the last address of an IPv4 CIDR, as numbers. */\nexport const cidrRange = (cidr: string): [number, number] => {\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\/(\\d+)$/.exec(cidr);\n if (match === null) throw new Error(`not an IPv4 CIDR: ${cidr}`);\n const octets = match.slice(1, 5).map(Number);\n const bits = Number(match[5]);\n if (octets.some((octet) => octet > 255) || bits > 32) throw new Error(`not an IPv4 CIDR: ${cidr}`);\n const base = octets.reduce((sum, octet) => sum * 256 + octet, 0);\n const size = 2 ** (32 - bits);\n const first = Math.floor(base / size) * size;\n return [first, first + size - 1];\n};\n\n/** This function tells if two IPv4 CIDRs share an address. */\nexport const cidrOverlap = (a: string, b: string): boolean => {\n const [a0, a1] = cidrRange(a);\n const [b0, b1] = cidrRange(b);\n return a0 <= b1 && b0 <= a1;\n};\n","import { createHash } from \"node:crypto\";\nimport { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { gunzipSync } from \"node:zlib\";\n\n/**\n * A tool is a binary that the lab downloads, at one pinned release, for each platform. The lab\n * keeps it in the cache folder of this package and does not install it on the system. Each use\n * compares its sha256 with the pinned value.\n *\n * A tool with an `entry` comes in a gzipped tar file. The pinned sha256 is the sha256 of that file.\n * The cache keeps the file, and each use takes the entry from it again.\n */\nexport interface Tool {\n readonly name: \"kind\" | \"kubectl\" | \"istioctl\";\n readonly version: string;\n /** These are the release assets, by the platform and the architecture of Node: `win32-x64`, `linux-x64`. */\n readonly assets: Readonly<Record<string, { readonly url: string; readonly sha256: string }>>;\n /** This name is the file of the tool in its gzipped tar file, or nothing for a tool that is its own asset. */\n readonly entry?: string | undefined;\n}\n\n/** This release of kind is v0.33.0, from 2026-08-26. Its default node image is Kubernetes 1.37.0. The sums are from its `.sha256sum` assets. */\nexport const KIND: Tool = {\n name: \"kind\",\n version: \"v0.33.0\",\n assets: {\n \"win32-x64\": {\n url: \"https://github.com/kubernetes-sigs/kind/releases/download/v0.33.0/kind-windows-amd64\",\n sha256: \"4b22adaa135368c5a465d56bbd8e520cbea87272a06ca00b6078e7b81515c9fc\",\n },\n \"linux-x64\": {\n url: \"https://github.com/kubernetes-sigs/kind/releases/download/v0.33.0/kind-linux-amd64\",\n sha256: \"aee6151561422756b764a4ae28e7f44cda5af5a9eead3cc9985112b1de8d8e0d\",\n },\n \"linux-arm64\": {\n url: \"https://github.com/kubernetes-sigs/kind/releases/download/v0.33.0/kind-linux-arm64\",\n sha256: \"20022bee6cfcd5086cb7234d218e3454e6090022f2a8f55d1fa7fcf42c3867a2\",\n },\n \"darwin-arm64\": {\n url: \"https://github.com/kubernetes-sigs/kind/releases/download/v0.33.0/kind-darwin-arm64\",\n sha256: \"0c8c7dbe5e23594a198b786c4bc13dacc101fa6196b0cb0b23a1ca44e61f4b4f\",\n },\n },\n};\n\n/** This release of kubectl is v1.37.0, the version of the node image. The sums are from the `.sha256` files of `dl.k8s.io`. */\nexport const KUBECTL: Tool = {\n name: \"kubectl\",\n version: \"v1.37.0\",\n assets: {\n \"win32-x64\": {\n url: \"https://dl.k8s.io/release/v1.37.0/bin/windows/amd64/kubectl.exe\",\n sha256: \"4721b614a67bb4932a0369e61f4a323d8c6ca00943d3a2ff14837c124da06f0e\",\n },\n \"linux-x64\": {\n url: \"https://dl.k8s.io/release/v1.37.0/bin/linux/amd64/kubectl\",\n sha256: \"6129359f4e1f3848a5572ccb0b26cf28b8ca08cef38c95a765b2f64a2c961a2f\",\n },\n \"linux-arm64\": {\n url: \"https://dl.k8s.io/release/v1.37.0/bin/linux/arm64/kubectl\",\n sha256: \"922df28df248cc00a9e025f947704f1d1482de64ece54cfe57e61f19eaf1eef3\",\n },\n \"darwin-arm64\": {\n url: \"https://dl.k8s.io/release/v1.37.0/bin/darwin/arm64/kubectl\",\n sha256: \"583beedaebe422e71d3f1a96acef8b1fef86ea2f09a45ad01aa6c9ce287c1380\",\n },\n },\n};\n\n/**\n * This release of istioctl is 1.31.3, from 2026-10-22. It holds the charts of its own version, thus the\n * lab needs no other tool to install Istio. The sums are from the `.sha256` assets of the release. The\n * release has a zip file for Windows, and the lab reads only a gzipped tar file. Thus Windows has no asset.\n */\nexport const ISTIOCTL: Tool = {\n name: \"istioctl\",\n version: \"1.31.3\",\n entry: \"istioctl\",\n assets: {\n \"linux-x64\": {\n url: \"https://github.com/istio/istio/releases/download/1.31.3/istioctl-1.31.3-linux-amd64.tar.gz\",\n sha256: \"382c3004ce2e9c5ed3064b092ae0da7f6bbe30c766a1be282f45725add004860\",\n },\n \"linux-arm64\": {\n url: \"https://github.com/istio/istio/releases/download/1.31.3/istioctl-1.31.3-linux-arm64.tar.gz\",\n sha256: \"7476801623dbb900073cb1dba6a23b6a15c564b7e92b208aed36829315fefff1\",\n },\n \"darwin-arm64\": {\n url: \"https://github.com/istio/istio/releases/download/1.31.3/istioctl-1.31.3-osx-arm64.tar.gz\",\n sha256: \"881c3b443d9e458e8f19feed6ffa187dcfcb03dc032cb56068f7d1d0cd976c82\",\n },\n },\n};\n\n/**\n * This function gives the bytes of one file of a gzipped tar file. A header of the ustar format has\n * 512 bytes. It has the name at 0, the size in octal at 124, the type at 156 and the prefix of the\n * name at 345. The data follows the header, in blocks of 512 bytes. The match is on the last part of the\n * name. A link, a folder and an extended header are not a file, thus the search skips them.\n */\nexport const tarEntry = (gzipped: Uint8Array, entry: string): Uint8Array => {\n const tar = gunzipSync(gzipped);\n const text = (from: number, length: number): string => {\n const field = tar.subarray(from, from + length);\n const end = field.indexOf(0);\n return field.subarray(0, end === -1 ? field.length : end).toString(\"latin1\");\n };\n let at = 0;\n while (at + 512 <= tar.length) {\n const name = text(at, 100);\n if (name === \"\") break;\n const size = Number.parseInt(text(at + 124, 12).trim() || \"0\", 8);\n if (!Number.isFinite(size)) throw new Error(`the tar entry ${name} has no size in octal`);\n const type = String.fromCharCode(tar[at + 156] ?? 0);\n const prefix = text(at + 345, 155);\n const full = prefix === \"\" ? name : `${prefix}/${name}`;\n if ((type === \"0\" || type === \"\\0\") && full.split(\"/\").at(-1) === entry) return new Uint8Array(tar.subarray(at + 512, at + 512 + size));\n at += 512 + Math.ceil(size / 512) * 512;\n }\n throw new Error(`the tar file has no file ${entry}`);\n};\n\n/** This folder is the cache of the tools: `.cache/` in this package, which git ignores. `KIND_CACHE` names another folder. */\nexport const cacheFolder = (): string =>\n process.env[\"KIND_CACHE\"] ?? fileURLToPath(new URL(\"../.cache/\", import.meta.url));\n\n/** This function gives the platform key of this process, as the assets name it. */\nexport const platformKey = (platform: string = process.platform, arch: string = process.arch): string =>\n `${platform}-${arch}`;\n\n/** This function gives the sha256 of some bytes, in hexadecimal. */\nexport const sha256 = (bytes: Uint8Array): string => createHash(\"sha256\").update(bytes).digest(\"hex\");\n\n/** This function gives the path of a tool in the cache, for a platform. */\nexport const toolPath = (tool: Tool, key: string = platformKey(), cache: string = cacheFolder()): string =>\n join(cache, \"tools\", `${tool.name}-${tool.version}-${key}`, key.startsWith(\"win32\") ? `${tool.name}.exe` : tool.name);\n\n/** This function writes a file through a partial file, thus a stop in the middle leaves no file at the path. */\nconst writeWhole = (path: string, bytes: Uint8Array, executable: boolean): void => {\n mkdirSync(dirname(path), { recursive: true });\n const partial = `${path}.partial`;\n writeFileSync(partial, bytes);\n if (executable) chmodSync(partial, 0o755);\n renameSync(partial, path);\n};\n\n/** These are the waits in ms before the second and the third try of a download. A runner can fail to connect for a moment. */\nexport const DOWNLOAD_WAITS: readonly number[] = [2_000, 8_000];\n\n/**\n * This function gives the bytes of a URL. It tries again after each wait when the connection fails or\n * the answer is a 5xx. It refuses each other answer that is not ok at once.\n */\nexport const download = async (url: string, waits: readonly number[] = DOWNLOAD_WAITS): Promise<Uint8Array> => {\n for (let attempt = 0; ; attempt++) {\n const response = await fetch(url, { redirect: \"follow\" }).catch((error: unknown) => (error instanceof Error ? error : new Error(String(error))));\n if (response instanceof Response && response.ok) return new Uint8Array(await response.arrayBuffer());\n const failure = response instanceof Response ? new Error(`the download of ${url} answered ${response.status}`) : response;\n const wait = waits[attempt];\n if ((response instanceof Response && response.status < 500) || wait === undefined) throw failure;\n await new Promise((resolve) => setTimeout(resolve, wait));\n }\n};\n\n/**\n * This function gives the path of a tool, after a download when the cache does not have it. It\n * refuses bytes whose sha256 is not the pinned value, from the cache or from the download. For a\n * tool in a tar file, the cache keeps the tar file beside the tool. Each use examines the tar file,\n * and writes the tool again when its bytes are not those of the entry.\n */\nexport const ensureTool = async (tool: Tool, key: string = platformKey(), cache: string = cacheFolder()): Promise<string> => {\n const asset = tool.assets[key];\n if (asset === undefined) throw new Error(`${tool.name} ${tool.version} has no pinned asset for ${key}`);\n const path = toolPath(tool, key, cache);\n const executable = !key.startsWith(\"win32\");\n // the downloaded asset: the tool itself, or its tar file\n const kept = tool.entry === undefined ? path : `${path}.tar.gz`;\n let bytes: Uint8Array;\n if (existsSync(kept)) {\n bytes = readFileSync(kept);\n const found = sha256(bytes);\n if (found !== asset.sha256) throw new Error(`${kept} has the sha256 ${found}, not the pinned ${asset.sha256}`);\n if (tool.entry === undefined) return path;\n } else {\n bytes = await download(asset.url);\n const found = sha256(bytes);\n if (found !== asset.sha256) throw new Error(`${asset.url} has the sha256 ${found}, not the pinned ${asset.sha256}`);\n writeWhole(kept, bytes, executable && tool.entry === undefined);\n if (tool.entry === undefined) return path;\n }\n const binary = tarEntry(bytes, tool.entry);\n if (!existsSync(path) || sha256(readFileSync(path)) !== sha256(binary)) writeWhole(path, binary, executable);\n return path;\n};\n","import { execFile } from \"node:child_process\";\nimport { mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport type { ClusterManifest } from \"./record.ts\";\nimport type { KindScenario } from \"./scenario.ts\";\nimport { NODE_IMAGES, cidrOverlap, kindConfig, nodeNames } from \"./scenario.ts\";\nimport { KIND, KUBECTL, ensureTool } from \"./tools.ts\";\n\nconst run = promisify(execFile);\n\n/** This function starts a program and gives its standard output. A failure gives its standard error in the message. */\nexport const command = async (\n program: string,\n args: readonly string[],\n options: { readonly env?: Readonly<Record<string, string>>; readonly input?: string; readonly timeout?: number } = {},\n): Promise<string> => {\n try {\n const child = run(program, [...args], {\n env: { ...process.env, ...options.env },\n maxBuffer: 256 << 20,\n timeout: options.timeout ?? 600_000,\n windowsHide: true,\n });\n if (options.input !== undefined) {\n child.child.stdin?.end(options.input);\n }\n const { stdout } = await child;\n return stdout.toString();\n } catch (error) {\n const failed = error as { stderr?: string; stdout?: string; message?: string };\n throw new Error(`${program} ${args.join(\" \")} failed: ${(failed.stderr ?? failed.message ?? \"\").toString().trim().slice(0, 2000)}`);\n }\n};\n\n/** The credentials of the API, as a container on the network of the cluster reaches it. */\nexport interface Credentials {\n readonly server: string;\n readonly ca: string;\n readonly cert: string;\n readonly key: string;\n}\n\n/** A cluster is one kind cluster that the lab made, with its own kubeconfig and Docker network. */\nexport interface Cluster {\n readonly scenario: KindScenario;\n readonly dir: string;\n readonly kubeconfig: string;\n readonly network: string;\n readonly nodes: readonly string[];\n kubectl(args: readonly string[], input?: string): Promise<string>;\n /** This method applies Kubernetes objects, as JSON on standard input. */\n apply(objects: readonly unknown[]): Promise<void>;\n /** This method starts a command in a node container. */\n exec(node: string, argv: readonly string[]): Promise<string>;\n /** This method copies a local Docker image into each node. */\n load(image: string): Promise<void>;\n credentials(): Promise<Credentials>;\n manifest(): Promise<ClusterManifest>;\n delete(): Promise<void>;\n}\n\n/** This function gives the subnets of the Docker networks, by their names. */\nconst dockerSubnets = async (): Promise<Array<[string, string]>> => {\n const ids = (await command(\"docker\", [\"network\", \"ls\", \"-q\"])).split(/\\s+/).filter((id) => id !== \"\");\n if (ids.length === 0) return [];\n const info = JSON.parse(await command(\"docker\", [\"network\", \"inspect\", ...ids])) as Array<{\n Name: string;\n IPAM?: { Config?: Array<{ Subnet?: string }> | null };\n }>;\n return info.flatMap((network) =>\n (network.IPAM?.Config ?? [])\n .map((config) => config.Subnet ?? \"\")\n .filter((subnet) => /^\\d+\\.\\d+\\.\\d+\\.\\d+\\/\\d+$/.test(subnet))\n .map((subnet): [string, string] => [network.Name, subnet]),\n );\n};\n\n/**\n * This function makes a kind cluster from a scenario, in a folder that holds its config, its\n * kubeconfig and the cache of kubectl. The cluster gets a Docker network of its own. The function\n * refuses a pod or service subnet that overlaps a Docker network, because that breaks the traffic\n * between the nodes.\n */\nexport const createCluster = async (scenario: KindScenario, dir: string): Promise<Cluster> => {\n const kind = await ensureTool(KIND);\n const kubectlPath = await ensureTool(KUBECTL);\n mkdirSync(dir, { recursive: true });\n const kubeconfig = join(dir, \"kubeconfig\");\n const network = `systems-${scenario.name}`;\n const config = kindConfig(scenario);\n for (const [name, subnet] of await dockerSubnets()) {\n if (name === network) continue;\n for (const ours of [scenario.podSubnet, scenario.serviceSubnet])\n if (cidrOverlap(ours, subnet)) throw new Error(`the subnet ${ours} overlaps the Docker network ${name} (${subnet})`);\n }\n const file = join(dir, \"kind.json\");\n writeFileSync(file, `${JSON.stringify(config, null, 2)}\\n`);\n const env = { KIND_EXPERIMENTAL_DOCKER_NETWORK: network };\n const nodes = nodeNames(scenario);\n const kubectl = (args: readonly string[], input?: string) =>\n command(kubectlPath, [\"--kubeconfig\", kubeconfig, \"--cache-dir\", join(dir, \"kubectl-cache\"), ...args], input === undefined ? {} : { input });\n const cluster: Cluster = {\n scenario,\n dir,\n kubeconfig,\n network,\n nodes,\n kubectl,\n async apply(objects) {\n await kubectl([\"apply\", \"-f\", \"-\"], JSON.stringify({ apiVersion: \"v1\", kind: \"List\", items: objects }));\n },\n exec: (node, argv) => command(\"docker\", [\"exec\", node, ...argv]),\n async load(image) {\n await command(kind, [\"load\", \"docker-image\", \"--name\", scenario.name, image], { env, timeout: 900_000 });\n },\n async credentials() {\n const internal = join(dir, \"kubeconfig.internal\");\n writeFileSync(internal, await command(kind, [\"get\", \"kubeconfig\", \"--internal\", \"--name\", scenario.name], { env }));\n const view = JSON.parse(\n await command(kubectlPath, [\"--kubeconfig\", internal, \"config\", \"view\", \"--raw\", \"-o\", \"json\"]),\n ) as {\n clusters: Array<{ cluster: { server: string; \"certificate-authority-data\": string } }>;\n users: Array<{ user: { \"client-certificate-data\": string; \"client-key-data\": string } }>;\n };\n return {\n server: view.clusters[0]!.cluster.server,\n ca: view.clusters[0]!.cluster[\"certificate-authority-data\"],\n cert: view.users[0]!.user[\"client-certificate-data\"],\n key: view.users[0]!.user[\"client-key-data\"],\n };\n },\n async manifest() {\n const nodesJson = JSON.parse(await kubectl([\"get\", \"nodes\", \"-o\", \"json\"])) as {\n items: Array<{ metadata: { name: string }; status: { nodeInfo: Record<string, string> } }>;\n };\n const proxy = JSON.parse(await kubectl([\"-n\", \"kube-system\", \"get\", \"configmap\", \"kube-proxy\", \"-o\", \"json\"])) as {\n data: Record<string, string>;\n };\n const mode = /^mode:\\s*(\\S*)/m.exec(proxy.data[\"config.conf\"] ?? \"\")?.[1] ?? \"\";\n const version = JSON.parse(await kubectl([\"version\", \"-o\", \"json\"])) as { serverVersion?: { gitVersion?: string } };\n return {\n kind: (await command(kind, [\"version\"])).trim(),\n nodeImage: NODE_IMAGES[scenario.kubernetes]!.image,\n server: version.serverVersion?.gitVersion ?? \"\",\n mode: mode.replaceAll('\"', \"\"),\n docker: (await command(\"docker\", [\"version\", \"--format\", \"{{.Server.Version}}\"])).trim(),\n nodes: nodesJson.items.map((item) => ({\n name: item.metadata.name,\n kubelet: item.status.nodeInfo[\"kubeletVersion\"] ?? \"\",\n kernel: item.status.nodeInfo[\"kernelVersion\"] ?? \"\",\n runtime: item.status.nodeInfo[\"containerRuntimeVersion\"] ?? \"\",\n os: item.status.nodeInfo[\"osImage\"] ?? \"\",\n })),\n };\n },\n async delete() {\n await command(kind, [\"delete\", \"cluster\", \"--name\", scenario.name, \"--kubeconfig\", kubeconfig], { env });\n await command(\"docker\", [\"network\", \"rm\", network]).catch(() => undefined);\n },\n };\n await command(\n kind,\n [\"create\", \"cluster\", \"--name\", scenario.name, \"--config\", file, \"--kubeconfig\", kubeconfig, \"--wait\", \"180s\"],\n { env, timeout: 900_000 },\n );\n return cluster;\n};\n","// The composition of the service of a kind cell. The app of the image and `lint()` compose the same\n// service from the same declaration: the image copies this file, and Node strips its types. Thus\n// this file imports only packages and types.\nimport type { CheckFn, HealthServer, ServiceBuilder, TransportImpl } from \"@mark1russell7/service\";\nimport { service } from \"@mark1russell7/service\";\nimport type { RedisClient } from \"@mark1russell7/service/redis\";\nimport type { Declaration } from \"./cell.ts\";\n\n/** These are the parts of a running service. A composition for `lint()` gives none of them. */\nexport interface Parts {\n readonly redis?: RedisClient | undefined;\n readonly flag?: CheckFn | undefined;\n readonly http?: TransportImpl | undefined;\n readonly health?: HealthServer | undefined;\n}\n\n/** This name is the controller of the service, and its one route reads Redis when the service has Redis. */\nexport const CONTROLLER = \"work\";\nexport const ROUTE = \"GET /work\";\n/** This name is the check that a flag file fails: the seam of the liveness restart. */\nexport const FLAG_CHECK = \"self.flag\";\n/** This name is the watchdog of the event loop. */\nexport const WATCHDOG = \"self.event-loop\";\n\n/**\n * This function composes the service of a declaration: its policies, its couplings, its checks,\n * one server, one controller and each stage of the shutdown. A declaration with Redis needs the\n * client, because the client declares the dependency and its checks.\n */\nexport const compose = (name: string, declaration: Declaration, parts: Parts = {}): ServiceBuilder => {\n const svc = service(name)\n .readiness(declaration.readiness)\n .liveness(declaration.liveness)\n .startup(declaration.startup)\n .cooperativeWindow(declaration.cooperativeWindow)\n // the kubelet sends SIGTERM one time and SIGKILL at the end of the grace: a repeat signal has no effect\n .onRepeatSignal(\"ignore\");\n for (const coupling of declaration.couplings) svc.couple(coupling);\n if (declaration.redis !== null) {\n if (parts.redis === undefined) throw new Error(`${name} declares Redis, thus its composition needs the client`);\n parts.redis.attach(svc, { poll: declaration.poll, steps: { \"round-trip\": declaration.redis.gates } });\n }\n if (declaration.flag !== null)\n svc.check(\n {\n name: FLAG_CHECK,\n dependency: null,\n step: \"local-state\",\n ownConnection: true,\n gates: declaration.flag,\n poll: declaration.poll,\n },\n parts.flag,\n );\n if (declaration.watchdog !== null)\n svc.watchdog({\n name: WATCHDOG,\n tick: declaration.watchdog.tick,\n threshold: declaration.watchdog.threshold,\n gates: \"liveness\",\n poll: declaration.poll,\n });\n svc.server(\"http\", {}, parts.http);\n const controller = svc.controller(CONTROLLER);\n if (declaration.redis !== null) controller.requires(parts.redis!.name);\n controller.route(ROUTE, { idempotent: true });\n if (parts.health !== undefined) svc.healthServer(parts.health);\n for (const [stage, cap] of declaration.phases) svc.phase(stage, cap);\n return svc;\n};\n","// The hosts of one cluster in the Envoy of a sidecar, from the text of its admin endpoint\n// `/clusters`. The agent of the lab image reads it with Node, thus this module imports nothing.\n\n/**\n * These are the hosts of a cluster: the healthy ones (`b`, in sequence), and each other host with its\n * health flags (`u`). Each host is `ip:port`.\n */\nexport interface ClusterHosts {\n readonly b: readonly string[];\n readonly u: Readonly<Record<string, string>>;\n}\n\n/**\n * This function gives the name of the outbound cluster of a Service port in a sidecar of Istio:\n * `outbound|<port>||<service>.<namespace>.svc.cluster.local`.\n */\nexport const outboundCluster = (service: string, namespace: string, port: number): string => `outbound|${port}||${service}.${namespace}.svc.cluster.local`;\n\n/** This function gives the filter of `/clusters` that selects one cluster: a regular expression of RE2 with anchors. */\nexport const clusterFilter = (cluster: string): string => `^${cluster.replace(/[.|\\\\^$*+?()[\\]{}]/g, (c) => `\\\\${c}`)}$`;\n\n/**\n * This function gives the hosts of a cluster from the text of `/clusters`. Each host of the text has\n * a line `<cluster>::<ip:port>::health_flags::<flags>`. The flags are `healthy`, or the names of the\n * flags that the host has (`clusters_handler.cc` and `host_utility.cc` of Envoy v1.39.4).\n */\nexport const hostsOf = (text: string, cluster: string): ClusterHosts => {\n const healthy: string[] = [];\n const other: Record<string, string> = {};\n const prefix = `${cluster}::`;\n for (const line of text.split(\"\\n\")) {\n if (!line.startsWith(prefix)) continue;\n const parts = line.slice(prefix.length).split(\"::\");\n if (parts.length !== 3 || parts[1] !== \"health_flags\") continue;\n const [host, , flags] = parts as [string, string, string];\n if (flags.trim() === \"healthy\") healthy.push(host);\n else other[host] = flags.trim();\n }\n return { b: healthy.sort(), u: other };\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { KindCell } from \"./cell.ts\";\nimport { CLIENT, REDIS, SERVER } from \"./names.ts\";\n\n/**\n * This module maps each trigger of a cell to the acts of the lab, as data. The runner finds each\n * target at the time of the run. A target is a pod, the PID of a container, the network namespace\n * of a pod, or a cgroup.\n */\n\n/**\n * A target is a process or a pod of the cell:\n * - `server` is the first replica of the service, in the sequence of its name.\n * - `redis` is the Redis pod.\n * - `kube-proxy` is kube-proxy on the first worker.\n * - `servers` is each replica of the service. Only the injector takes it: a rule in the network\n * namespace of each replica, with each client pod as a peer. A REJECT resets only the sender of\n * the segment that it matches, thus the rule goes where the segments of the clients arrive.\n * - `clients` is the process of each client pod. Only a signal takes it: the gap of the trigger `idle`.\n */\nexport type Target = \"server\" | \"redis\" | \"kube-proxy\" | \"servers\" | \"clients\";\n\n/** A step is one act of a fault, in the sequence of the plan. */\nexport type Step =\n | { readonly do: \"kubectl\"; readonly args: readonly string[]; readonly background?: boolean }\n /**\n * This step sends a signal to the target. For the target `clients`, it sends it to each client\n * process, the nodes at the same time. SIGUSR1 stops the calls of a gRPC client, and SIGUSR2 starts them again.\n */\n | { readonly do: \"signal\"; readonly target: Target; readonly signal: \"SIGKILL\" | \"SIGSTOP\" | \"SIGCONT\" | \"SIGUSR1\" | \"SIGUSR2\" }\n /** This step sends SIGSTOP, then SIGCONT `time` later, as the VM times it. The next step starts at once. */\n | { readonly do: \"pause\"; readonly target: Target; readonly time: Quantity }\n /**\n * This step starts the injector of the harness in the namespace of the target, with each replica of\n * the service as a peer. For the target `servers`, it starts it in the namespace of each replica,\n * with each client pod as a peer. The rules of all replicas commit at one instant.\n */\n | { readonly do: \"inject\"; readonly target: Target; readonly action: \"blackhole\" | \"clear\" | \"rst\" | \"clear-rst\" }\n | { readonly do: \"flag\"; readonly target: Target; readonly on: boolean }\n | { readonly do: \"cpu\"; readonly target: Target; readonly max: string }\n /** This step blocks the event loop of the target for `time`. */\n | { readonly do: \"stall\"; readonly target: Target; readonly time: Quantity }\n | { readonly do: \"wait\"; readonly time: Quantity }\n /** This step applies one object with `kubectl apply`. The runner fills the marks of its strings, for example `{namespace}`. */\n | { readonly do: \"apply\"; readonly object: Readonly<Record<string, unknown>> };\n\n/**\n * A fault plan is the steps of a run:\n * - `inject` starts the trigger. The stamp of its first act is the `fault` reference.\n * - `heal` ends the fault, `hold` after the fault. A trigger that ends by itself has no heal.\n * - `restore` brings the cell back to its steady state after the run.\n */\nexport interface FaultPlan {\n readonly inject: readonly Step[];\n readonly hold: Quantity | null;\n readonly heal: readonly Step[];\n readonly restore: readonly Step[];\n}\n\nconst needs = (time: Quantity | undefined, what: string): Quantity => {\n if (time === undefined) throw new Error(`the trigger needs ${what}`);\n return time;\n};\n\n/** This function gives an amount in microseconds as a whole number, for `cpu.max`. */\nconst micros = (amount: Quantity): number => Math.round(valueIn(amount, \"us\"));\n\n/** This value marks the name of the target pod in the arguments of kubectl. The runner replaces it. */\nexport const POD = \"{pod}\";\n/** This value marks the node of the target pod in the arguments of kubectl. */\nexport const NODE = \"{node}\";\n/** This value marks the next version of the Deployment, for a rollout. */\nexport const VERSION = \"{version}\";\n/** This annotation of a pod is its deletion cost for its ReplicaSet: a lower cost goes first at a scale-in. */\nexport const DELETION_COST = \"controller.kubernetes.io/pod-deletion-cost\";\n/** This name is the name of the AuthorizationPolicy of the trigger `authorization-policy`. */\nexport const POLICY = \"lab-deny\";\n/** This service account is the account of the clients: their pods name no other account. */\nexport const CLIENT_ACCOUNT = \"default\";\n\n/**\n * This function gives the AuthorizationPolicy of the trigger `authorization-policy`. It denies each\n * request to the server pods from the principal of the service account `account`. Istio gives a\n * pod of the mesh the principal `cluster.local/ns/<namespace>/sa/<account>` of its account. The\n * RBAC filter of the server sidecar reads the principal of the peer from its mTLS certificate.\n */\nexport const policyObject = (account: string): Readonly<Record<string, unknown>> => ({\n apiVersion: \"security.istio.io/v1\",\n kind: \"AuthorizationPolicy\",\n metadata: { name: POLICY, namespace: \"{namespace}\" },\n spec: {\n selector: { matchLabels: { app: SERVER } },\n action: \"DENY\",\n rules: [{ from: [{ source: { principals: [`cluster.local/ns/{namespace}/sa/${account}`] } }] }],\n },\n});\n\n/**\n * This function gives the target of a reset or a black hole of a cell. In T07 and T18 it is Redis,\n * with the replicas as peers. In T15 the fault is on the mesh leg between the client pods and the\n * replicas. It is a rule in each replica, with the client pods as peers.\n */\nconst linkOf = (cell: KindCell): Target => (cell.template === \"T15\" ? \"servers\" : \"redis\");\n\n/** This function gives the fault plan of a cell. */\nexport const planOf = (cell: KindCell): FaultPlan => {\n const ns = [\"-n\", \"{namespace}\"];\n const trigger = cell.trigger;\n const replicas = cell.pod.replicas;\n switch (trigger.kind) {\n case \"pod-deletion\":\n return { inject: [{ do: \"kubectl\", args: [...ns, \"delete\", \"pod\", POD, \"--wait=false\"] }], hold: null, heal: [], restore: [] };\n case \"rolling-update\":\n return {\n inject: [{ do: \"kubectl\", args: [...ns, \"set\", \"env\", `deployment/${SERVER}`, `LAB_VERSION=${VERSION}`] }],\n hold: null,\n heal: [],\n restore: [],\n };\n case \"node-drain\":\n return {\n inject: [\n {\n do: \"kubectl\",\n args: [\"drain\", NODE, \"--ignore-daemonsets\", \"--delete-emptydir-data\", \"--timeout=180s\", `--pod-selector=app=${SERVER}`],\n background: true,\n },\n ],\n hold: null,\n heal: [],\n restore: [{ do: \"kubectl\", args: [\"uncordon\", NODE] }],\n };\n case \"scale-in\":\n return {\n inject: [\n // the ReplicaSet removes a pod of a lower deletion cost first. The rank by readiness comes before\n // it, and the pods of a node come after it (pkg/controller/controller_utils.go at v1.37.0, lines\n // 853–874). The gate PodDeletionCost is on from 1.22 (pkg/features/kube_features.go, line 1953).\n // Thus the clients of gRPC on the first replica see its stop\n ...(cell.stack === \"grpc\" ? [{ do: \"kubectl\" as const, args: [...ns, \"annotate\", \"pod\", POD, `${DELETION_COST}=-1`, \"--overwrite\"] }] : []),\n { do: \"kubectl\", args: [...ns, \"scale\", `deployment/${SERVER}`, `--replicas=${replicas - 1}`] },\n ],\n hold: null,\n heal: [],\n restore: [{ do: \"kubectl\", args: [...ns, \"scale\", `deployment/${SERVER}`, `--replicas=${replicas}`] }],\n };\n case \"crash\":\n return { inject: [{ do: \"signal\", target: \"server\", signal: \"SIGKILL\" }], hold: null, heal: [], restore: [] };\n case \"liveness-restart\":\n // the container that the kubelet starts again has a new /tmp, thus the flag ends with it\n return { inject: [{ do: \"flag\", target: \"server\", on: true }], hold: null, heal: [], restore: [] };\n case \"freeze-process\":\n if (cell.template === \"T07\") {\n return {\n inject: [\n { do: \"pause\", target: \"kube-proxy\", time: needs(trigger.hold, \"a hold\") },\n { do: \"wait\", time: needs(trigger.lead, \"a lead\") },\n { do: \"kubectl\", args: [...ns, \"delete\", \"pod\", POD, \"--wait=false\"] },\n ],\n hold: null,\n heal: [],\n restore: [],\n };\n }\n return {\n inject: [{ do: \"signal\", target: \"redis\", signal: \"SIGSTOP\" }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"signal\", target: \"redis\", signal: \"SIGCONT\" }],\n restore: [],\n };\n case \"rst\":\n return {\n inject: [{ do: \"inject\", target: linkOf(cell), action: \"rst\" }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"inject\", target: linkOf(cell), action: \"clear-rst\" }],\n restore: [],\n };\n case \"blackhole\":\n return {\n inject: [{ do: \"inject\", target: linkOf(cell), action: \"blackhole\" }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"inject\", target: linkOf(cell), action: \"clear\" }],\n restore: [],\n };\n case \"peer-gone-without-close\":\n // the rules drop the FIN of Redis, then Redis goes away with its namespace and its rules\n return {\n inject: [\n { do: \"inject\", target: \"redis\", action: \"blackhole\" },\n { do: \"kubectl\", args: [...ns, \"scale\", `deployment/${REDIS}`, \"--replicas=0\"] },\n ],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"kubectl\", args: [...ns, \"scale\", `deployment/${REDIS}`, \"--replicas=1\"] }],\n restore: [],\n };\n case \"app-stall\":\n return { inject: [{ do: \"stall\", target: \"server\", time: needs(trigger.hold, \"a hold\") }], hold: null, heal: [], restore: [] };\n case \"rollout\":\n // Redis is down for each pod, a new one too: the Service of Redis has no endpoint\n return {\n inject: [\n { do: \"kubectl\", args: [...ns, \"scale\", `deployment/${REDIS}`, \"--replicas=0\"] },\n { do: \"kubectl\", args: [...ns, \"set\", \"env\", `deployment/${SERVER}`, `LAB_VERSION=${VERSION}`] },\n ],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"kubectl\", args: [...ns, \"scale\", `deployment/${REDIS}`, \"--replicas=1\"] }],\n restore: [],\n };\n case \"cpu-throttle\": {\n const max = trigger.cpuMax;\n if (max === undefined) throw new Error(\"the trigger needs its cpu.max\");\n // the kernel reads cpu.max as the quota and the period in microseconds\n return {\n inject: [{ do: \"cpu\", target: \"server\", max: `${micros(max.quota)} ${micros(max.period)}` }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"cpu\", target: \"server\", max: `max ${micros(max.period)}` }],\n restore: [],\n };\n }\n case \"authorization-policy\":\n // the restore deletes a policy that a failed heal left\n return {\n inject: [{ do: \"apply\", object: policyObject(trigger.account ?? CLIENT_ACCOUNT) }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"kubectl\", args: [...ns, \"delete\", \"authorizationpolicy\", POLICY] }],\n restore: [{ do: \"kubectl\", args: [...ns, \"delete\", \"authorizationpolicy\", POLICY, \"--ignore-not-found\"] }],\n };\n case \"idle\":\n // the gap of the clients: the restore starts the calls of a client that a failed heal left stopped\n return {\n inject: [{ do: \"signal\", target: \"clients\", signal: \"SIGUSR1\" }],\n hold: needs(trigger.hold, \"a hold, the time of the gap\"),\n heal: [{ do: \"signal\", target: \"clients\", signal: \"SIGUSR2\" }],\n restore: [{ do: \"signal\", target: \"clients\", signal: \"SIGUSR2\" }],\n };\n }\n};\n\n/** This function tells if a trigger stops a pod of the service with its grace: a deletion, an eviction, a scale-in or a rollout. */\nexport const deletes = (cell: KindCell): boolean =>\n [\"pod-deletion\", \"rolling-update\", \"node-drain\", \"scale-in\"].includes(cell.trigger.kind) ||\n (cell.template === \"T07\" && cell.trigger.kind === \"freeze-process\");\n\n/** These are the names of the objects of a cell that a plan names. */\nexport const OBJECTS: { readonly server: typeof SERVER; readonly client: typeof CLIENT; readonly redis: typeof REDIS } = {\n server: SERVER,\n client: CLIENT,\n redis: REDIS,\n};\n","import { execFileSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/** This folder is the root of the package, and its `image/` folder has the files of the image. */\nconst PACKAGE = fileURLToPath(new URL(\"../\", import.meta.url));\n/** This folder is the root of the repository, when the package is in the workspace of the repository. */\nconst REPO = fileURLToPath(new URL(\"../../../\", import.meta.url));\n/** These are the packages of this repository that the image holds, in the sequence of their dependencies. */\nexport const WORKSPACE_PACKAGES: readonly [\"quantity\", \"algebra\", \"service\"] = [\"quantity\", \"algebra\", \"service\"];\n/** These are the files of the image, by their name in the context and their source in the package. */\nconst FILES: ReadonlyArray<readonly [string, string]> = [\n [\"Dockerfile\", \"image/Dockerfile\"],\n [\"package.json\", \"image/package.json\"],\n [\"npm-lock.json\", \"image/npm-lock.json\"],\n [\"app.mjs\", \"image/app.mjs\"],\n [\"client.mjs\", \"image/client.mjs\"],\n [\"client-grpc.mjs\", \"image/client-grpc.mjs\"],\n [\"agent.mjs\", \"image/agent.mjs\"],\n [\"watch.mjs\", \"image/watch.mjs\"],\n [\"compose.ts\", \"src/compose.ts\"],\n [\"backends.ts\", \"src/backends.ts\"],\n [\"conntrack.ts\", \"src/conntrack.ts\"],\n [\"drops.ts\", \"src/drops.ts\"],\n [\"eds.ts\", \"src/eds.ts\"],\n [\"lds.ts\", \"src/lds.ts\"],\n [\"client-lines.ts\", \"src/client-lines.ts\"],\n];\n\nconst shell = process.platform === \"win32\";\n\n/** This function gives each file of a staged context, with its path in the context, in a stable sequence. */\nconst filesOf = (root: string, at = \"\"): string[] =>\n readdirSync(join(root, at), { withFileTypes: true })\n .flatMap((entry) => (entry.isDirectory() ? filesOf(root, join(at, entry.name)) : [join(at, entry.name)]))\n .sort();\n\n/** This function gives the tag of a staged context from its content: the name, then 12 digits of a sha256. */\nexport const contentTag = (name: string, root: string): string => {\n const hash = createHash(\"sha256\");\n for (const file of filesOf(root)) {\n hash.update(file.replaceAll(\"\\\\\", \"/\"));\n hash.update(readFileSync(join(root, file)));\n }\n return `${name}:${hash.digest(\"hex\").slice(0, 12)}`;\n};\n\n/** This function tells if the package is in the workspace of its repository. From npm, it is not. */\nconst inWorkspace = (): boolean =>\n existsSync(join(REPO, \"pnpm-workspace.yaml\")) && WORKSPACE_PACKAGES.every((name) => existsSync(join(REPO, \"packages\", name, \"package.json\")));\n\n/**\n * This function gives the folder of each installed package that the image holds. Node finds a\n * package from the folder of a package that depends on it. The service depends on the algebra,\n * thus the search starts with the service.\n */\nconst installedFolders = (): Map<string, string> => {\n const folders = new Map<string, string>();\n for (const name of [...WORKSPACE_PACKAGES].reverse()) {\n const found = [PACKAGE, ...folders.values()].flatMap((from) => {\n try {\n return [dirname(realpathSync(createRequire(join(from, \"package.json\")).resolve(`@mark1russell7/${name}/package.json`)))];\n } catch {\n return [];\n }\n })[0];\n if (found === undefined) throw new Error(`@mark1russell7/${name} is not installed with @mark1russell7/kind`);\n folders.set(name, found);\n }\n return folders;\n};\n\n/**\n * This function stages the build context of the lab image. In the workspace, it builds the packages\n * of the repository and packs each one as npm gets it. From npm, it packs the installed packages.\n * Then it copies the files of the image.\n */\nexport const stageLabImage = (): string => {\n const context = mkdtempSync(join(tmpdir(), \"systems-kind-lab-\"));\n const packed = join(context, \"workspace\");\n mkdirSync(packed);\n if (inWorkspace()) {\n const filters = WORKSPACE_PACKAGES.flatMap((name) => [\"--filter\", `@mark1russell7/${name}`]);\n execFileSync(\"pnpm\", [...filters, \"build\"], { cwd: REPO, shell, stdio: \"ignore\" });\n for (const name of WORKSPACE_PACKAGES)\n execFileSync(\"pnpm\", [\"pack\", \"--pack-destination\", packed], { cwd: join(REPO, \"packages\", name), shell, stdio: \"ignore\" });\n } else {\n for (const folder of installedFolders().values())\n execFileSync(\"npm\", [\"pack\", \"--pack-destination\", packed, \"--ignore-scripts\"], { cwd: folder, shell, stdio: \"ignore\" });\n }\n for (const [at, from] of FILES) copyFileSync(join(PACKAGE, from), join(context, at));\n return context;\n};\n\nlet built: Promise<string> | null = null;\n\n/**\n * This function builds the lab image one time for each process, and gives its tag. Docker keeps its\n * layers between builds. A build that fails leaves the cache. Thus a later call builds again after a\n * fault of Docker or of the network.\n */\nexport const labImage = (): Promise<string> => {\n if (built !== null) return built;\n const building = (async () => {\n const context = stageLabImage();\n try {\n const tag = contentTag(\"systems-kind-lab\", context);\n const { GenericContainer } = await import(\"testcontainers\");\n await GenericContainer.fromDockerfile(context).build(tag, { deleteOnExit: false });\n return tag;\n } finally {\n rmSync(context, { recursive: true, force: true });\n }\n })();\n // a failed build does not stay: the next caller tries again\n building.catch(() => {\n if (built === building) built = null;\n });\n built = building;\n return building;\n};\n","import { writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport type { KindCell, MeshFlavor } from \"./cell.ts\";\nimport type { Cluster } from \"./cluster.ts\";\nimport { command } from \"./cluster.ts\";\nimport { outboundCluster } from \"./eds.ts\";\nimport { SERVER } from \"./names.ts\";\nimport { ISTIOCTL, ensureTool } from \"./tools.ts\";\n\n/**\n * This module installs Istio 1.31.3 in a kind cluster with istioctl, from images pinned by digest.\n * The release notes of 1.31.2 and 1.31.3 change no default that a cell of T15 reads. Each value\n * of the install that is not a default of Istio is in `istioOperator`, with its reason.\n */\n\n/** This version is the release of Istio of the lab: the tag of istioctl and of each image. */\nexport const ISTIO_VERSION = \"1.31.3\";\n\n/**\n * These are the images of Istio, pinned by the digest of their index on Docker Hub (read on\n * 2026-10-24). `gcr.io/istio-release` has no final tag of 1.31. The lab pulls each one by its digest,\n * tags it with that digest (`localPin`), and loads it into each node. Thus no node pulls from a\n * registry. The flavor `sidecar` needs istiod and the proxy. The flavor `ambient` also needs\n * istio-cni and ztunnel (`MESH_IMAGES`).\n */\nexport const ISTIO_IMAGES: { readonly pilot: string; readonly proxy: string; readonly cni: string; readonly ztunnel: string } = {\n pilot: \"istio/pilot:1.31.3@sha256:6ad1e816c4d4767ba3f30a6286a96d121851ddb23d33e20736758acb5f3f4ea9\",\n proxy: \"istio/proxyv2:1.31.3@sha256:eb74eacea98d3fb1e239ea84016085155478586a554158676b9b67d960151084\",\n cni: \"istio/install-cni:1.31.3@sha256:e98782038634ef4fadaf78b53dd8fa4134fda3d65838ead5fc1edef245451c0f\",\n ztunnel: \"istio/ztunnel:1.31.3@sha256:2c27371b3139ecbf4f5a017354bd540889482441712e88cd8e654ead5a3eb5b2\",\n};\n\n/** These are the images that each flavor installs, by their names in `ISTIO_IMAGES`. */\nexport const MESH_IMAGES: Readonly<Record<MeshFlavor, ReadonlyArray<keyof typeof ISTIO_IMAGES>>> = {\n sidecar: [\"pilot\", \"proxy\"],\n ambient: [\"pilot\", \"proxy\", \"cni\", \"ztunnel\"],\n};\n\n/**\n * This format is the JSON access log of each sidecar. Each field is a command of the formatter of\n * Envoy v1.39.4. The fields give the status, the response flags and their details, and the gRPC\n * status as a number. They also give the tries of the router, the upstream host and cluster, and\n * the reasons of a failed connection. The watcher reads `code`, `flags`, `grpc` and `attempts`\n * (`image/watch.mjs`).\n */\nexport const ACCESS_LOG_FORMAT: Readonly<Record<string, string>> = {\n start: \"%START_TIME%\",\n code: \"%RESPONSE_CODE%\",\n flags: \"%RESPONSE_FLAGS%\",\n details: \"%RESPONSE_CODE_DETAILS%\",\n grpc: \"%GRPC_STATUS_NUMBER%\",\n attempts: \"%UPSTREAM_REQUEST_ATTEMPT_COUNT%\",\n host: \"%UPSTREAM_HOST%\",\n cluster: \"%UPSTREAM_CLUSTER%\",\n local: \"%DOWNSTREAM_LOCAL_ADDRESS%\",\n remote: \"%DOWNSTREAM_REMOTE_ADDRESS%\",\n duration: \"%DURATION%\",\n failure: \"%UPSTREAM_TRANSPORT_FAILURE_REASON%\",\n termination: \"%CONNECTION_TERMINATION_DETAILS%\",\n};\n\n/** These are the local tags of the images, after the pull by digest. The flavor `ambient` needs `cni` and `ztunnel`. */\nexport interface MeshImages {\n readonly pilot: string;\n readonly proxy: string;\n readonly cni?: string | undefined;\n readonly ztunnel?: string | undefined;\n}\n\n/**\n * This function gives the IstioOperator of a flavor. The values that are not the defaults of Istio:\n * - The profile `minimal` is istiod only. The profile `default` also has an ingress gateway, which no cell uses.\n * - Each image is a full reference, thus the charts use it as it is (`contains \"/\"` in the\n * templates of istiod and of the injector). The init container of the injector has the image of the proxy too (`proxy_init`).\n * - `imagePullPolicy: Never`: each image is on each node. An image that the lab did not load fails the install at once.\n * - istiod requests 256 MiB, not 2 GiB, and has one replica with no autoscaler. Each node of kind\n * reports the whole runner, and no cell scales istiod.\n * - The access log of each sidecar goes to its output as JSON (`ACCESS_LOG_FORMAT`).\n *\n * The flavor `ambient` has the profile `ambient`: istiod, istio-cni and ztunnel, and no gateway\n * (manifests/profiles/ambient.yaml of Istio 1.31.3). Its two other images are full references too\n * (templates/daemonset.yaml of the chart of istio-cni, lines 93–96, and of ztunnel, lines 65–68).\n * The chart of istio-cni takes the pull policy of `global`. The template of ztunnel reads only its own\n * `imagePullPolicy`, thus the operator sets it there too.\n */\nexport const istioOperator = (flavor: MeshFlavor, images: MeshImages): Record<string, unknown> => {\n const ambient = flavor === \"ambient\";\n if (ambient && (images.cni === undefined || images.ztunnel === undefined)) throw new Error(\"the flavor ambient needs the images of istio-cni and ztunnel\");\n return {\n apiVersion: \"install.istio.io/v1alpha1\",\n kind: \"IstioOperator\",\n metadata: { name: \"lab\", namespace: \"istio-system\" },\n spec: {\n profile: ambient ? \"ambient\" : \"minimal\",\n hub: \"docker.io/istio\",\n tag: ISTIO_VERSION,\n meshConfig: {\n accessLogFile: \"/dev/stdout\",\n accessLogEncoding: \"JSON\",\n accessLogFormat: JSON.stringify(ACCESS_LOG_FORMAT),\n },\n values: {\n global: { imagePullPolicy: \"Never\", proxy: { image: images.proxy }, proxy_init: { image: images.proxy } },\n pilot: { image: images.pilot, autoscaleEnabled: false, replicaCount: 1, resources: { requests: { cpu: \"500m\", memory: \"256Mi\" } } },\n ...(ambient ? { cni: { image: images.cni }, ztunnel: { image: images.ztunnel, imagePullPolicy: \"Never\" } } : {}),\n },\n },\n };\n};\n\n/** These are the workloads of each flavor in istio-system that the install waits for. */\nexport const MESH_WORKLOADS: Readonly<Record<MeshFlavor, readonly string[]>> = {\n sidecar: [\"deployment/istiod\"],\n ambient: [\"deployment/istiod\", \"daemonset/istio-cni-node\", \"daemonset/ztunnel\"],\n};\n\n/**\n * This function installs the mesh of a flavor in a cluster with `istioctl install` and the operator\n * of the flavor. Then it waits for each workload of the flavor. The kubeconfig of the cluster goes to\n * istioctl. When the install fails, the error has the pods of istio-system and the last lines of the\n * log of istio-cni. Thus the log of the job shows the cause, for example a module that the kernel of\n * the runner does not have.\n */\nexport const installMesh = async (cluster: Cluster, flavor: MeshFlavor, images: MeshImages): Promise<void> => {\n const istioctl = await ensureTool(ISTIOCTL);\n const file = join(cluster.dir, `istio-${flavor}.json`);\n writeFileSync(file, `${JSON.stringify(istioOperator(flavor, images), null, 2)}\\n`);\n try {\n await command(istioctl, [\"install\", \"--kubeconfig\", cluster.kubeconfig, \"-y\", \"-f\", file, \"--readiness-timeout\", \"10m0s\"], { timeout: 900_000 });\n for (const workload of MESH_WORKLOADS[flavor]) await cluster.kubectl([\"-n\", \"istio-system\", \"rollout\", \"status\", workload, \"--timeout=300s\"]);\n } catch (error) {\n const state = async (args: readonly string[]): Promise<string> =>\n (await cluster.kubectl([\"-n\", \"istio-system\", ...args]).catch((why: unknown) => String(why))).slice(-4000);\n const pods = await state([\"get\", \"pods\", \"-o\", \"wide\"]);\n const cni = flavor === \"ambient\" ? `\\nthe log of istio-cni:\\n${await state([\"logs\", \"daemonset/istio-cni-node\", \"--tail=60\", \"--all-containers\"])}` : \"\";\n throw new Error(`the install of Istio ${flavor} failed: ${String(error)}\\nthe pods of istio-system:\\n${pods}${cni}`);\n }\n};\n\n/** This function gives the key of the mesh of a cell for its cluster: its flavor, or `none`. */\nexport const meshKey = (cell: Pick<KindCell, \"mesh\">): MeshFlavor | \"none\" => cell.mesh?.flavor ?? \"none\";\n\n/** This function gives the outbound cluster of the server of a cell in each client sidecar: the port 80 of its Service. */\nexport const serverCluster = (namespace: string): string => outboundCluster(SERVER, namespace, 80);\n\n/** This time is the interval of the EDS poll of each client sidecar, the same as the poll of the rules. */\nexport const EDS_EVERY: Quantity = { value: 50, unit: \"ms\" };\n\n/** This time is the interval of the LDS poll of each server sidecar, the same as the poll of EDS. */\nexport const LDS_EVERY: Quantity = { value: 50, unit: \"ms\" };\n\n/** This function tells if the watcher follows the log of each ztunnel for a cell: a cell in ambient mode. */\nexport const followsZtunnel = (cell: Pick<KindCell, \"mesh\">): boolean => cell.mesh?.mode === \"ambient\";\n\n/** This function tells if the lab polls the LDS updates of the server sidecars of a cell: a policy in a sidecar mesh. */\nexport const pollsListeners = (cell: Pick<KindCell, \"mesh\" | \"trigger\">): boolean => cell.mesh?.mode === \"sidecar\" && cell.trigger.kind === \"authorization-policy\";\n","// This module has no imports: the agent of the lab image imports it as it is.\n\n/**\n * This filter of RE2 selects the two counters of the listener manager of Envoy that an update of LDS\n * moves (`/stats?filter=`, `source/server/admin/stats_params.cc` of Envoy v1.39.4). An update of a\n * listener counts `listener_modified`. An update in place, of its filter chains only, also counts\n * `listener_in_place_updated` (`listener_manager_impl.cc:644`, `:692`).\n */\nexport const LDS_FILTER = \"^listener_manager\\\\.listener_(in_place_updated|modified)$\";\n\n/** The listener counts are the two counters of LDS of one sidecar: each update (`mod`), and each update in place (`inPlace`). */\nexport interface ListenerCounts {\n readonly mod: number;\n readonly inPlace: number;\n}\n\n/**\n * This function gives the listener counts of the text of `/stats`, one `name: value` on each line\n * (`stats_render.cc:15`). It gives null when the text has no counter `listener_modified`.\n */\nexport const listenerCountsOf = (text: string): ListenerCounts | null => {\n let mod: number | null = null;\n let inPlace = 0;\n for (const line of text.split(\"\\n\")) {\n const match = /^listener_manager\\.listener_(in_place_updated|modified): (\\d+)$/.exec(line.trim());\n if (match === null) continue;\n if (match[1] === \"modified\") mod = Number(match[2]);\n else inPlace = Number(match[2]);\n }\n return mod === null ? null : { mod, inPlace };\n};\n","// The parser of the conntrack table of a node. The agent of each node reads the table with it, and\n// the image copies this file, thus it imports nothing.\n\n/** A flow is one conntrack entry of TCP: its state, its life in seconds, its original tuple and the source of its replies. */\nexport interface ConntrackFlow {\n readonly state: string;\n readonly ttl: number;\n /** This address is the original source of the flow, as `ip:port`. */\n readonly from: string;\n /** This address is the original destination of the flow, as `ip:port`: a ClusterIP before its DNAT. */\n readonly to: string;\n /** This address is the source of the replies, as `ip:port`: the backend that the DNAT of the entry chose. */\n readonly reply: string;\n /** This flag tells that no packet came back on the flow. */\n readonly unreplied: boolean;\n}\n\n/** An entry is one conntrack entry of a TCP flow to the ClusterIP. It has a state, a life in seconds, a client and the backend of its NAT. */\nexport interface ConntrackEntry {\n readonly state: string;\n readonly ttl: number;\n /** This address is the original source of the flow: the client, as `ip:port`. */\n readonly client: string;\n /** This address is the source of the replies: the backend that the DNAT of the entry chose, as `ip:port`. */\n readonly backend: string;\n /** This flag tells that no packet came back on the flow. */\n readonly unreplied: boolean;\n}\n\n/**\n * These states keep the NAT of an entry for a new SYN on the same port (nf_conntrack_proto_tcp.c at\n * v6.8). A SYN on an entry in TIME_WAIT removes the entry and looks up again (:1011-1027), thus that\n * SYN gets a backend from the rules. In each other state, the SYN goes to the backend of the entry.\n * A CLOSE that a reset from the server made does not reopen either.\n */\nexport const KEEPS_NAT: ReadonlySet<string> = new Set([\"SYN_SENT\", \"SYN_SENT2\", \"SYN_RECV\", \"ESTABLISHED\", \"FIN_WAIT\", \"CLOSE_WAIT\", \"LAST_ACK\", \"CLOSE\"]);\n\n/**\n * These are the lives of an entry in the states of a handshake, in seconds\n * (nf_conntrack_tcp_timeout_syn_sent and _syn_recv). A retransmission of the SYN does not renew the\n * life of SYN_SENT (nf_conntrack_proto_tcp.c at v6.8). Thus the life of such an entry tells the age\n * of its first SYN.\n */\nexport const HANDSHAKE_LIFE: Readonly<Record<string, number>> = { SYN_SENT: 120, SYN_RECV: 60 };\n\n/** This form is one TCP line of `/proc/net/nf_conntrack` or `conntrack -L`. It has the timeout and state, the original tuple, the flags and counters, then the reply tuple. */\nconst LINE = /\\btcp\\s+\\d+\\s+(\\d+)\\s+(\\S+)\\s+src=(\\S+) dst=(\\S+) sport=(\\d+) dport=(\\d+)(.*?)\\bsrc=(\\S+) dst=\\S+ sport=(\\d+) dport=\\d+/;\n\n/** This function gives the TCP flows of a dump of the conntrack table. */\nexport const flowsOf = (text: string): ConntrackFlow[] => {\n const out: ConntrackFlow[] = [];\n for (const line of text.split(\"\\n\")) {\n const match = LINE.exec(line);\n if (match === null) continue;\n out.push({\n ttl: Number(match[1]),\n state: match[2]!,\n from: `${match[3]}:${match[5]}`,\n to: `${match[4]}:${match[6]}`,\n reply: `${match[8]}:${match[9]}`,\n unreplied: match[7]!.includes(\"[UNREPLIED]\"),\n });\n }\n return out;\n};\n\n/** This function gives the TCP entries of a dump of the conntrack table whose original destination is the ClusterIP and port. */\nexport const entriesOf = (text: string, ip: string, port: number): ConntrackEntry[] =>\n flowsOf(text)\n .filter((flow) => flow.to === `${ip}:${port}`)\n .map((flow) => ({ state: flow.state, ttl: flow.ttl, client: flow.from, backend: flow.reply, unreplied: flow.unreplied }));\n\n/**\n * This function gives the entries that keep their NAT to a pod that is not a backend of the rules.\n * A new SYN from the client port of such an entry goes to that pod, whatever the rules say.\n */\nexport const staleOf = (entries: readonly ConntrackEntry[], backends: readonly string[]): ConntrackEntry[] =>\n entries.filter((entry) => KEEPS_NAT.has(entry.state) && !backends.includes(entry.backend));\n\n/** This function gives the count of entries that `conntrack -D` deleted, from its last line: \"N flow entries have been deleted\". */\nexport const deletedOf = (text: string): number => {\n const match = /(\\d+) flow entr(?:y|ies) ha(?:s|ve) been deleted/.exec(text);\n if (match === null) throw new Error(`conntrack -D gave no count: ${text.trim().slice(0, 200)}`);\n return Number(match[1]);\n};\n\n/**\n * This function gives the flows whose handshake stopped `age` seconds ago or more. Such a flow is\n * a SYN with no reply (SYN_SENT), or a SYN and its SYN-ACK with no ACK (SYN_RECV). It is a\n * connection that times out. On the node of the client, its reply address is the backend of its\n * DNAT. On the node of that backend, a SYN_SENT tells that the backend did not answer. There, a\n * SYN_RECV tells that its answer did not come back.\n */\nexport const stalledOf = (flows: readonly ConntrackFlow[], age: number): ConntrackFlow[] =>\n flows.filter((flow) => {\n const life = HANDSHAKE_LIFE[flow.state];\n if (life === undefined || (flow.state === \"SYN_SENT\" && !flow.unreplied)) return false;\n return flow.ttl <= life - age;\n });\n","import type { StartedTestContainer } from \"testcontainers\";\nimport { harnessImage } from \"@mark1russell7/harness\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { ServiceRule } from \"./backends.ts\";\nimport { deletedOf } from \"./conntrack.ts\";\nimport type { Cluster } from \"./cluster.ts\";\nimport { command } from \"./cluster.ts\";\nimport type { Line } from \"./observe.ts\";\n\nexport type { Line } from \"./observe.ts\";\n\n/** A stamp brackets an act on CLOCK_MONOTONIC, in milliseconds. */\nexport interface Stamp {\n readonly t0: number;\n readonly t1: number;\n}\n\n/** These kinds of kernel record stay in the raw record of a run. The lab counts the other kinds and drops them. */\nexport const KEPT_KERNEL: ReadonlySet<string> = new Set([\"rst_tx\", \"rst_rx\", \"rst_v4\", \"error\", \"retrans\", \"begin\", \"fail\"]);\n\n/** An EDS spec is the cluster that the EDS pollers read, their interval, and each client pod with its node and a PID in it. */\nexport interface EdsSpec {\n readonly cluster: string;\n readonly every: Quantity;\n readonly pods: ReadonlyArray<{ readonly node: string; readonly pod: string; readonly pid: number }>;\n}\n\n/** An LDS spec is the interval of the LDS pollers, and each server pod with its node and a PID in it. */\nexport interface LdsSpec {\n readonly every: Quantity;\n readonly pods: ReadonlyArray<{ readonly node: string; readonly pod: string; readonly pid: number }>;\n}\n\n/** These are the sidecars of one node: the agent of the lab and the injector of the harness, in the PID namespace of the node. */\ninterface Sidecars {\n readonly agent: StartedTestContainer;\n readonly injector: StartedTestContainer;\n}\n\n/** A kind lab is the instruments of one cluster: a sidecar pair on each node, the watcher of the API and the prober. */\nexport interface KindLab {\n readonly cluster: Cluster;\n readonly image: string;\n /** These are the lines since the last `take()`. */\n readonly lines: Line[];\n /** This record counts the kernel records that the lab dropped, by kind. */\n readonly dropped: Record<string, number>;\n /** This method gives the names of the containers that the lab started: they are not the lab containers of another agent. */\n own(): string[];\n /** This method gives the lines since the last call, and starts a new buffer. */\n take(): Line[];\n /** This method adds a listener of each line, and gives the function that removes it. */\n listen(listener: (line: Line) => void): () => void;\n /**\n * This method starts the watcher of the API on the namespaces. With `ztunnel`, it also follows the\n * log of each ztunnel in istio-system: lines `ztunnel-pod` and `ztunnel`, which no tracker of the\n * cell reads.\n */\n watch(namespaces: readonly string[], options?: { readonly ztunnel?: boolean | undefined }): Promise<void>;\n probe(netns: readonly number[]): Promise<void>;\n stopProbe(): Promise<void>;\n /** This method asks each agent to poll the backends of a Service in the rules of its node, `every` apart, or to stop with null. */\n rules(rule: (ServiceRule & { readonly every: Quantity }) | null): Promise<void>;\n /**\n * This method asks each agent to poll the sidecar of each client pod of its node, `every` apart.\n * Each poll gives the hosts of one cluster. With null, the polls stop. A pod gives the PID of one\n * of its processes, and the agent enters the network namespace of that PID.\n */\n eds(spec: EdsSpec | null): Promise<void>;\n /**\n * This method asks each agent to poll the sidecar of each server pod of its node, `every` apart.\n * Each poll gives the counters of LDS updates of that sidecar (`lds.ts`). With null, the polls stop.\n */\n lds(spec: LdsSpec | null): Promise<void>;\n act(node: string, spec: Readonly<Record<string, unknown>>): Promise<Stamp>;\n inject(node: string, pid: number, args: readonly string[]): Promise<Stamp>;\n /**\n * This method gives the rules of one table of iptables in the network namespace of a PID, with\n * their counters (`iptables-save -c`). The injector reads them. The method also adds them to the\n * lines as a line `tables`: evidence of which rules matched.\n */\n tables(node: string, pid: number, table: string): Promise<string>;\n now(): Promise<number>;\n pidOf(node: string, namespace: string, pod: string, container: string): Promise<number>;\n netnsOf(node: string, pid: number): Promise<number>;\n /** This method gives the path of the cgroup of a process, as the agent reaches it through the root of the node. */\n cgroupOf(node: string, pid: number): Promise<string>;\n /**\n * This method deletes the conntrack entries in SYN_SENT to a ClusterIP and port on each node, and\n * gives the count of each node. Such an entry is a SYN with no answer. It keeps its NAT for 120 s,\n * and a new SYN from the same port goes where it went.\n */\n forgetHandshakes(ip: string, port: number): Promise<Record<string, number>>;\n close(): Promise<void>;\n}\n\n/** This container class can share the process namespace of another container. */\nconst shared = async () => {\n const { GenericContainer } = await import(\"testcontainers\");\n return class extends GenericContainer {\n withPidMode(mode: string): this {\n (this as unknown as { hostConfig: { PidMode?: string } }).hostConfig.PidMode = mode;\n return this;\n }\n };\n};\n\n/** This function collects the JSON lines of a stream, and gives each one to `take`. */\nconst consume = (take: (line: Record<string, unknown>) => void) => (stream: NodeJS.ReadableStream) => {\n let partial = \"\";\n stream.on(\"data\", (chunk: Buffer | string) => {\n partial += chunk.toString();\n const split = partial.split(\"\\n\");\n partial = split.pop() ?? \"\";\n for (const text of split) {\n const trimmed = text.trim();\n if (!trimmed.startsWith(\"{\")) continue;\n try {\n take(JSON.parse(trimmed) as Record<string, unknown>);\n } catch {\n // a line that is not whole JSON is not a record\n }\n }\n });\n};\n\n/** This function gives a stamp of the injector of the harness, in nanoseconds, as milliseconds. */\nconst injectorStamp = (stdout: string): Stamp => {\n const stamp = JSON.parse(stdout.trim().split(\"\\n\").at(-1)!) as { t0: number | string; t1: number | string };\n return { t0: Number(BigInt(stamp.t0)) / 1e6, t1: Number(BigInt(stamp.t1)) / 1e6 };\n};\n\nconst output = async (container: StartedTestContainer, argv: readonly string[]): Promise<string> => {\n const result = await container.exec([...argv]);\n if (result.exitCode !== 0) throw new Error(`${argv.join(\" \")} exited ${result.exitCode}: ${result.output.trim().slice(0, 500)}`);\n return result.stdout;\n};\n\n/**\n * This function opens the instruments of a cluster. Each node gets the agent of the lab and the\n * injector of the harness, privileged and in the PID namespace of the node. Thus the agent signals\n * a process of a pod by its PID on the node. The injector enters the network namespace of a pod\n * with `nsenter`.\n */\nexport const openKindLab = async (cluster: Cluster, image: string): Promise<KindLab> => {\n const harness = await harnessImage();\n const Shared = await shared();\n const { GenericContainer } = await import(\"testcontainers\");\n let lines: Line[] = [];\n const dropped: Record<string, number> = {};\n const listeners = new Set<(line: Line) => void>();\n const push = (line: Line): void => {\n lines.push(line);\n for (const listener of listeners) listener(line);\n };\n const sidecars = new Map<string, Sidecars>();\n const opened: StartedTestContainer[] = [];\n let watcher: StartedTestContainer | null = null;\n let prober: StartedTestContainer | null = null;\n try {\n for (const node of cluster.nodes) {\n const agent = await new Shared(image)\n .withPidMode(`container:${node}`)\n .withPrivilegedMode()\n .withNetworkMode(\"none\")\n .withEnvironment({ NODE_NAME: node })\n .withCommand([\"node\", \"agent.mjs\", \"serve\"])\n .withLogConsumer(consume((line) => push({ ...(line as unknown as Line), node })))\n .start();\n opened.push(agent);\n const injector = await new Shared(harness)\n .withPidMode(`container:${node}`)\n .withPrivilegedMode()\n .withNetworkMode(\"none\")\n .start();\n opened.push(injector);\n sidecars.set(node, { agent, injector });\n }\n } catch (error) {\n for (const container of opened.reverse()) await container.stop({ timeout: 0 }).catch(() => undefined);\n throw error;\n }\n const sidecarOf = (node: string): Sidecars => {\n const found = sidecars.get(node);\n if (found === undefined) throw new Error(`the cluster has no node ${node}`);\n return found;\n };\n const firstNode = cluster.nodes[0]!;\n /**\n * This function writes the file of the pollers of one kind on each agent: `fields`, the interval\n * and the pods of its node. A node without a pod, or a spec of null, gets no file.\n */\n const pollersOf = async (\n kind: \"eds\" | \"lds\",\n fields: Readonly<Record<string, unknown>> | null,\n spec: { readonly every: Quantity; readonly pods: LdsSpec[\"pods\"] } | null,\n ): Promise<void> => {\n for (const [node, { agent }] of sidecars) {\n const pods = spec === null ? [] : spec.pods.filter((pod) => pod.node === node).map(({ pod, pid }) => ({ pod, pid }));\n // the agent reads the interval of its poll in milliseconds\n const file = spec === null || pods.length === 0 ? null : JSON.stringify({ ...fields, every: valueIn(spec.every, \"ms\"), pods });\n if (file === null) await output(agent, [\"rm\", \"-f\", `/tmp/${kind}.json`]);\n // a rename is atomic, thus the agent never reads a part of the file\n else await output(agent, [\"sh\", \"-c\", `printf '%s' '${file}' > /tmp/${kind}.json.partial && mv /tmp/${kind}.json.partial /tmp/${kind}.json`]);\n }\n };\n const lab: KindLab = {\n cluster,\n image,\n get lines() {\n return lines;\n },\n dropped,\n own() {\n return [...opened, ...(watcher === null ? [] : [watcher]), ...(prober === null ? [] : [prober])].map((container) => container.getName().replace(/^\\//, \"\"));\n },\n take() {\n const out = lines;\n lines = [];\n return out;\n },\n listen(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n async watch(namespaces, options) {\n if (watcher !== null) await watcher.stop({ timeout: 0 }).catch(() => undefined);\n const kube = await cluster.credentials();\n watcher = await new GenericContainer(image)\n .withNetworkMode(cluster.network)\n .withEnvironment({ KUBE: JSON.stringify(kube), NAMESPACES: namespaces.join(\",\"), ZTUNNEL: options?.ztunnel === true ? \"1\" : \"0\" })\n .withCommand([\"node\", \"watch.mjs\"])\n .withLogConsumer(consume((line) => push(line as unknown as Line)))\n .start();\n },\n async probe(netns) {\n await lab.stopProbe();\n let begun: (ok: boolean, why: string) => void = () => {};\n const ready = new Promise<void>((resolve, reject) => {\n begun = (ok, why) => (ok ? resolve() : reject(new Error(`the prober failed: ${why}`)));\n });\n prober = await new GenericContainer(harness)\n .withPrivilegedMode()\n .withNetworkMode(\"none\")\n .withCommand([\"/opt/harness/prober\", ...netns.map(String)])\n .withLogConsumer(\n consume((record) => {\n const ev = String(record[\"ev\"]);\n if (ev === \"begin\") begun(true, \"\");\n if (ev === \"fail\") begun(false, String(record[\"why\"]));\n if (!KEPT_KERNEL.has(ev) && ev !== \"clock\") {\n dropped[ev] = (dropped[ev] ?? 0) + 1;\n return;\n }\n const mono = Number(record[\"mono\"] ?? 0);\n push({ ...(record as unknown as Line), ev: `kernel.${ev}`, t: mono / 1e6 });\n }),\n )\n .start();\n await Promise.race([\n ready,\n new Promise((_, reject) => setTimeout(() => reject(new Error(\"no begin line from the prober in 60 s\")), 60_000)),\n ]);\n },\n async stopProbe() {\n if (prober !== null) await prober.stop({ timeout: 0 }).catch(() => undefined);\n prober = null;\n },\n async rules(rule) {\n // the agent reads the interval of its poll in milliseconds\n const file = rule === null ? null : JSON.stringify({ ...rule, every: valueIn(rule.every, \"ms\") });\n for (const { agent } of sidecars.values()) {\n if (file === null) await output(agent, [\"rm\", \"-f\", \"/tmp/rules.json\"]);\n else await output(agent, [\"sh\", \"-c\", `printf '%s' '${file}' > /tmp/rules.json`]);\n }\n },\n async eds(spec) {\n await pollersOf(\"eds\", spec === null ? null : { cluster: spec.cluster }, spec);\n },\n async lds(spec) {\n await pollersOf(\"lds\", {}, spec);\n },\n async forgetHandshakes(ip, port) {\n const out: Record<string, number> = {};\n for (const [node, { agent }] of sidecars) {\n const argv = [\"nsenter\", \"-t\", \"1\", \"-m\", \"-n\", \"--\", \"conntrack\", \"-D\", \"-p\", \"tcp\", \"--orig-dst\", ip, \"--dport\", String(port), \"--state\", \"SYN_SENT\"];\n // the tool writes its count to stderr, and exits 1 when it deletes no entry\n const result = await agent.exec(argv);\n if (result.exitCode !== 0 && result.exitCode !== 1) throw new Error(`${argv.join(\" \")} exited ${result.exitCode}: ${result.output.trim().slice(0, 300)}`);\n out[node] = deletedOf(result.output);\n }\n return out;\n },\n async act(node, spec) {\n const stdout = await output(sidecarOf(node).agent, [\"node\", \"agent.mjs\", \"act\", JSON.stringify(spec)]);\n const result = JSON.parse(stdout.trim().split(\"\\n\").at(-1)!) as { ok: boolean; t0: number; t1: number; why?: string };\n if (!result.ok) throw new Error(`the act ${JSON.stringify(spec)} on ${node} failed: ${result.why}`);\n push({ ev: \"act\", t: result.t0, t1: result.t1, node, spec });\n return { t0: result.t0, t1: result.t1 };\n },\n async inject(node, pid, args) {\n const stdout = await output(sidecarOf(node).injector, [\"nsenter\", \"-t\", String(pid), \"-n\", \"/opt/harness/inject\", ...args]);\n const stamp = injectorStamp(stdout);\n push({ ev: \"act\", t: stamp.t0, t1: stamp.t1, node, spec: { op: \"inject\", pid, args } });\n return stamp;\n },\n async tables(node, pid, table) {\n const t = await lab.now();\n const text = await output(sidecarOf(node).injector, [\"nsenter\", \"-t\", String(pid), \"-n\", \"iptables-save\", \"-c\", \"-t\", table]);\n push({ ev: \"tables\", t, node, pid, table, text: text.slice(0, 8000) });\n return text;\n },\n async now() {\n return (await lab.act(firstNode, { op: \"now\" })).t0;\n },\n async pidOf(node, namespace, pod, container) {\n const ids = (\n await cluster.exec(node, [\n \"crictl\",\n \"ps\",\n \"-q\",\n \"--state\",\n \"running\",\n \"--label\",\n `io.kubernetes.pod.namespace=${namespace}`,\n \"--label\",\n `io.kubernetes.pod.name=${pod}`,\n \"--name\",\n `^${container}$`,\n ])\n )\n .split(/\\s+/)\n .filter((id) => id !== \"\");\n if (ids.length !== 1) throw new Error(`${namespace}/${pod}/${container} on ${node} has ${ids.length} running containers, not 1`);\n const info = JSON.parse(await cluster.exec(node, [\"crictl\", \"inspect\", ids[0]!])) as { info?: { pid?: number } };\n const pid = info.info?.pid;\n if (pid === undefined || !(pid > 1)) throw new Error(`crictl gives no PID for ${namespace}/${pod}/${container}`);\n return pid;\n },\n async netnsOf(node, pid) {\n const link = (await cluster.exec(node, [\"readlink\", `/proc/${pid}/ns/net`])).trim();\n const inode = /^net:\\[(\\d+)\\]$/.exec(link)?.[1];\n if (inode === undefined) throw new Error(`not a network namespace: ${link}`);\n return Number(inode);\n },\n async cgroupOf(node, pid) {\n const text = (await cluster.exec(node, [\"cat\", `/proc/${pid}/cgroup`])).trim();\n const path = /^0::(\\S+)$/m.exec(text)?.[1];\n // an empty or a short path is the root of the node: a write there throttles or freezes each pod of the node\n if (path === undefined || !/\\/cri-containerd-[0-9a-f]{64}\\.scope$/.test(path))\n throw new Error(`the cgroup of ${pid} on ${node} is not the scope of one container: ${text}`);\n return `/proc/1/root/sys/fs/cgroup${path}`;\n },\n async close() {\n await lab.stopProbe();\n if (watcher !== null) await watcher.stop({ timeout: 0 }).catch(() => undefined);\n watcher = null;\n for (const container of opened.reverse()) await container.stop({ timeout: 0 }).catch(() => undefined);\n },\n };\n return lab;\n};\n\n/** This function pins a Docker image by digest under a local tag that holds the digest, for `kind load` and `imagePullPolicy: Never`. */\nexport const localPin = async (image: string): Promise<string> => {\n const match = /^([^@]+?)(?::([^@:/]+))?@sha256:([0-9a-f]{64})$/.exec(image);\n if (match === null) throw new Error(`${image} is not pinned by a digest`);\n const [, repository, tag, digest] = match;\n const local = `systems-lab/${repository!.split(\"/\").at(-1)}:${tag ?? \"pinned\"}-${digest!.slice(0, 12)}`;\n const present = await command(\"docker\", [\"image\", \"inspect\", \"--format\", \"{{json .RepoDigests}}\", `${repository}@sha256:${digest}`]).catch(() => \"\");\n if (!present.includes(digest!)) await command(\"docker\", [\"pull\", `${repository}@sha256:${digest}`], { timeout: 900_000 });\n await command(\"docker\", [\"tag\", `${repository}@sha256:${digest}`, local]);\n return local;\n};\n","import { createHash } from \"node:crypto\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { KindCell, KubeletProbe, MeshMode, Stack } from \"./cell.ts\";\nimport { CLIENT, HEALTH_PORT, LOG_LABEL, PORT, REDIS, SERVER } from \"./names.ts\";\n\n/**\n * This module generates the Kubernetes objects of a cell. They are its namespace, its Redis, the\n * server as a Deployment behind a Service, and one client on each node. The server is the service\n * or grpc-go, by the stack of the cell. Each setting of the pod comes from the cell. The rollout\n * strategy and the probe fields state the defaults of Kubernetes as values.\n */\n\n/** This function gives the stack of a cell: the service, when the cell names no stack. */\nexport const stackOf = (cell: Pick<KindCell, \"stack\">): Stack => cell.stack ?? \"service\";\n\nexport { CLIENT, HEALTH_PORT, LOG_LABEL, PORT, REDIS, SERVER } from \"./names.ts\";\n\n/** This image is Redis 7.4, the stock target of the harness, pinned by digest. */\nexport const REDIS_IMAGE = \"redis:7.4@sha256:c6eabf748fc7a61dbb5a705c78bcf3d6377b1127a97d0ce965c11c44ba46896f\";\n\nconst whole = (amount: Quantity, what: string): number => {\n const seconds = valueIn(amount, \"s\");\n if (!Number.isInteger(seconds) || seconds < 0) throw new RangeError(`${what} is ${seconds} s, not a whole number of seconds`);\n return seconds;\n};\n\n/** This function gives the namespace of a cell: `lab-` and 10 digits of the sha256 of its name. */\nexport const namespaceOf = (cell: Pick<KindCell, \"name\">): string =>\n `lab-${createHash(\"sha256\").update(cell.name).digest(\"hex\").slice(0, 10)}`;\n\n/** These are the labels of the namespace of each mode of a mesh. */\nconst MESH_LABELS: Readonly<Record<MeshMode, Readonly<Record<string, string>>>> = {\n sidecar: { \"istio-injection\": \"enabled\" },\n ambient: { \"istio.io/dataplane-mode\": \"ambient\" },\n};\n\n/**\n * This function gives the Namespace object of a cell. In the sidecar mode of a mesh, the label\n * `istio-injection=enabled` makes the injector of Istio put a sidecar in each pod of the namespace.\n * In the ambient mode, the label `istio.io/dataplane-mode=ambient` makes istio-cni redirect the\n * traffic of each pod to ztunnel. The listeners of ztunnel are in the network namespace of the pod.\n */\nexport const namespaceObject = (cell: Pick<KindCell, \"mesh\">, namespace: string): Record<string, unknown> => ({\n apiVersion: \"v1\",\n kind: \"Namespace\",\n metadata: { name: namespace, ...(cell.mesh === undefined ? {} : { labels: { ...MESH_LABELS[cell.mesh.mode] } }) },\n});\n\n/**\n * This function gives a probe of the pod, with each field of the cell. The probe of the service is an\n * HTTP GET of its health server (`path`). The probe of grpc-go (`path` null) is the gRPC probe of the\n * kubelet. It is a Check of the service \"\" on the port of the server, and the answer is SERVING until\n * GracefulStop. A gRPC probe can name another service (`service`): the field `service` of `GRPCAction`\n * (`staging/src/k8s.io/api/core/v1/types.go` at v1.37.0, line 2892).\n */\nexport const probeOf = (path: string | null, probe: KubeletProbe, service?: string): Record<string, unknown> => ({\n ...(path === null ? { grpc: service === undefined ? { port: PORT } : { port: PORT, service } } : { httpGet: { path, port: HEALTH_PORT } }),\n initialDelaySeconds: 0,\n periodSeconds: whole(probe.period, `the period of ${path ?? \"the gRPC probe\"}`),\n timeoutSeconds: whole(probe.timeout, `the timeout of ${path ?? \"the gRPC probe\"}`),\n failureThreshold: probe.threshold,\n successThreshold: 1,\n});\n\n/** This variable is the name of the pod, from the API: the service and grpc-go put it in their lines. */\nconst POD_ENV = { name: \"POD_NAME\", valueFrom: { fieldRef: { fieldPath: \"metadata.name\" } } };\n\n/** This function gives the environment of the app: its config as JSON, and the name of its pod. */\nconst appEnv = (cell: KindCell): Array<Record<string, unknown>> => [\n { name: \"CONFIG\", value: JSON.stringify({ name: SERVER, port: PORT, healthPort: HEALTH_PORT, declaration: cell.declaration }) },\n POD_ENV,\n];\n\n/** This file is the flag of the trigger `liveness-restart`, in the root of the server process (`run.ts`, the step `flag`). */\nconst FLAG_FILE = \"/tmp/lab-fail\";\n\n/**\n * This function gives the container of the server of a cell. The service runs `app.mjs` of the lab\n * image with its declaration. The server grpc-go runs from the image of the harness. Its lines name\n * the pod and stamp in milliseconds. Its replies name the pod in the header `x-pod`. Kubernetes\n * expands `$(POD_NAME)` in the command.\n *\n * The seam of `liveness-restart` in grpc-go is its service `liveness`, which the flag file fails\n * (`--fail-file`). Thus for that trigger the liveness probe is a Check of `liveness`. The readiness\n * probe stays a Check of \"\", which the flag file does not fail.\n */\nconst serverContainer = (cell: KindCell, version: number): Record<string, unknown> => {\n const env = { name: \"LAB_VERSION\", value: String(version) };\n if (stackOf(cell) === \"service\")\n return {\n command: [\"node\", \"app.mjs\"],\n env: [...appEnv(cell), env],\n ports: [{ containerPort: PORT }, { containerPort: HEALTH_PORT }],\n ...(cell.pod.startup === null ? {} : { startupProbe: probeOf(\"/startup\", cell.pod.startup) }),\n readinessProbe: probeOf(\"/readiness\", cell.pod.readiness),\n livenessProbe: probeOf(\"/liveness\", cell.pod.liveness),\n };\n const seam = cell.trigger.kind === \"liveness-restart\";\n return {\n command: [\"grpc-go-server\", \"--port\", String(PORT), \"--name\", \"$(POD_NAME)\", \"--clock\", \"ms\", ...(seam ? [\"--fail-file\", FLAG_FILE] : [])],\n env: [POD_ENV, env],\n ports: [{ containerPort: PORT }],\n ...(cell.pod.startup === null ? {} : { startupProbe: probeOf(null, cell.pod.startup) }),\n readinessProbe: probeOf(null, cell.pod.readiness),\n livenessProbe: seam ? probeOf(null, cell.pod.liveness, \"liveness\") : probeOf(null, cell.pod.liveness),\n };\n};\n\n/** These are the objects of Redis: one replica behind a Service, with no persistence. */\nexport const redisObjects = (namespace: string, image: string): unknown[] => [\n {\n apiVersion: \"apps/v1\",\n kind: \"Deployment\",\n metadata: { name: REDIS, namespace, labels: { app: REDIS } },\n spec: {\n replicas: 1,\n selector: { matchLabels: { app: REDIS } },\n template: {\n metadata: { labels: { app: REDIS } },\n spec: {\n terminationGracePeriodSeconds: 1,\n containers: [\n {\n name: REDIS,\n image,\n imagePullPolicy: \"Never\",\n command: [\"redis-server\", \"--save\", \"\", \"--appendonly\", \"no\"],\n ports: [{ containerPort: 6379 }],\n readinessProbe: { tcpSocket: { port: 6379 }, periodSeconds: 1, failureThreshold: 1, successThreshold: 1, timeoutSeconds: 1 },\n },\n ],\n },\n },\n },\n },\n {\n apiVersion: \"v1\",\n kind: \"Service\",\n metadata: { name: REDIS, namespace },\n spec: { selector: { app: REDIS }, ports: [{ port: 6379, targetPort: 6379 }] },\n },\n];\n\n/**\n * This function gives the preStop hook of a sleep, or nothing for no sleep. The hook is the native\n * sleep action of the kubelet. The API server refuses a sleep action that is longer than the grace\n * (`validateSleepAction`, `pkg/apis/core/validation/validation.go` at v1.37.0, line 3483). Thus a\n * longer sleep is a command in the container, `sleep` from `/bin/sh`, as public manifests write it.\n * The kubelet starts that command with no timeout (`pkg/kubelet/lifecycle/handlers.go`, line 81).\n */\nconst preStopOf = (preStop: number, grace: number): Record<string, unknown> => {\n if (preStop === 0) return {};\n if (preStop <= grace) return { lifecycle: { preStop: { sleep: { seconds: preStop } } } };\n return { lifecycle: { preStop: { exec: { command: [\"/bin/sh\", \"-c\", `sleep ${preStop}`] } } } };\n};\n\n/**\n * These are the objects of the server: a Service of type ClusterIP, and the Deployment with the\n * settings of the cell. The image is the lab image for the service, or the image of grpc-go.\n */\nexport const serverObjects = (cell: KindCell, namespace: string, image: string, version = 1): unknown[] => {\n const preStop = whole(cell.pod.preStop, \"the preStop sleep\");\n const grace = whole(cell.pod.grace, \"the grace period\");\n return [\n {\n apiVersion: \"v1\",\n kind: \"Service\",\n metadata: { name: SERVER, namespace },\n spec: { selector: { app: SERVER }, ports: [{ name: stackOf(cell) === \"grpc\" ? \"grpc\" : \"http\", port: 80, targetPort: PORT }] },\n },\n {\n apiVersion: \"apps/v1\",\n kind: \"Deployment\",\n metadata: { name: SERVER, namespace, labels: { app: SERVER } },\n spec: {\n replicas: cell.pod.replicas,\n selector: { matchLabels: { app: SERVER } },\n strategy: { type: \"RollingUpdate\", rollingUpdate: { maxSurge: \"25%\", maxUnavailable: \"25%\" } },\n minReadySeconds: 0,\n template: {\n metadata: { labels: { app: SERVER, [LOG_LABEL]: \"1\", \"lab.systems/version\": String(version) } },\n spec: {\n terminationGracePeriodSeconds: grace,\n enableServiceLinks: true,\n topologySpreadConstraints: [\n {\n maxSkew: 1,\n topologyKey: \"kubernetes.io/hostname\",\n whenUnsatisfiable: \"ScheduleAnyway\",\n labelSelector: { matchLabels: { app: SERVER } },\n },\n ],\n containers: [\n {\n name: \"app\",\n image,\n imagePullPolicy: \"Never\",\n ...serverContainer(cell, version),\n ...preStopOf(preStop, grace),\n },\n ],\n },\n },\n },\n },\n ];\n};\n\n/**\n * These are the objects of the clients: a DaemonSet with one client on each node, the control plane\n * too. The client of the service is `client.mjs`, and the client of grpc-go is `client-grpc.mjs`.\n * Each one is in the lab image, and each one writes the same lines.\n */\nexport const clientObjects = (cell: KindCell, namespace: string, image: string, target: string): unknown[] => [\n {\n apiVersion: \"apps/v1\",\n kind: \"DaemonSet\",\n metadata: { name: CLIENT, namespace, labels: { app: CLIENT } },\n spec: {\n selector: { matchLabels: { app: CLIENT } },\n template: {\n metadata: { labels: { app: CLIENT, [LOG_LABEL]: \"1\" } },\n spec: {\n terminationGracePeriodSeconds: 0,\n tolerations: [{ key: \"node-role.kubernetes.io/control-plane\", operator: \"Exists\", effect: \"NoSchedule\" }],\n containers: [\n {\n name: CLIENT,\n image,\n imagePullPolicy: \"Never\",\n command: [\"node\", stackOf(cell) === \"grpc\" ? \"client-grpc.mjs\" : \"client.mjs\"],\n env: [\n { name: \"TARGET\", value: target },\n { name: \"EVERY_MS\", value: String(valueIn(cell.workload.every, \"ms\")) },\n { name: \"TIMEOUT_MS\", value: String(valueIn(cell.workload.timeout, \"ms\")) },\n { name: \"POD_NAME\", valueFrom: { fieldRef: { fieldPath: \"metadata.name\" } } },\n { name: \"NODE_NAME\", valueFrom: { fieldRef: { fieldPath: \"spec.nodeName\" } } },\n ],\n },\n ],\n },\n },\n },\n },\n];\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { Poll, Stage, Timing } from \"@mark1russell7/service\";\nimport { failSpan } from \"@mark1russell7/service\";\nimport { bandOf, seconds, secondsOf } from \"./bands.ts\";\nimport type { Band, Declaration, KubeletProbe } from \"./cell.ts\";\n\n/**\n * This module is the arithmetic of the predictions. Each band is a range of times after a\n * reference event of the run. Each term comes from a declaration, from the pod settings, or from\n * the calibration of the propagation. No term comes from a default. The arithmetic operates in\n * seconds, and each result is a quantity.\n */\n\nconst s = (amount: Quantity): number => valueIn(amount, \"s\");\n/** This function rounds a time to the microsecond, thus a band has no float residue. */\nconst us = (value: number): number => Math.round(value * 1e6) / 1e6;\nconst band = (lo: number, hi: number): Band => bandOf(us(lo), us(hi));\n\n/**\n * A propagation is the time from a change of an endpoint to the rules of each node. A\n * calibration measures it for each kube-proxy mode and node count. Without a calibration, its upper\n * bound is `minSyncPeriod`: an idle kube-proxy syncs a change at once, or one `minSyncPeriod`\n * after its last sync.\n */\nexport interface Propagation extends Band {\n /** This text tells where the bound comes from: a calibration, or the documented `minSyncPeriod`. */\n readonly from: string;\n}\n\n/** This propagation is the documented bound, before a calibration: from zero to one `minSyncPeriod` of 1 s. */\nexport const DOCUMENTED: Propagation = { lo: seconds(0), hi: seconds(1), from: \"minSyncPeriod (1 s), not calibrated\" };\n\n/**\n * This function gives the time from a flip of the probed state to the decision of the kubelet. The\n * next probe comes within one period, then `threshold − 1` more periods follow. A probe of an\n * endpoint that does not answer fails at its timeout.\n */\nexport const kubeletBand = (probe: KubeletProbe, hangs: boolean): Band => {\n const late = hangs ? s(probe.timeout) : 0;\n return band((probe.threshold - 1) * s(probe.period) + late, probe.threshold * s(probe.period) + late);\n};\n\n/**\n * This function gives the time from a fault of a dependency to the trip of a readiness or liveness\n * latch. A dependency that refuses fails each poll at once. A silent dependency fails each poll at\n * the request timeout of its client, or at the timeout of the poll when that is shorter. The\n * watchdog of the client can close the connection sooner, and then each poll fails at once.\n */\nexport const latchBand = (\n timing: Timing,\n poll: Poll,\n failure: \"refuses\" | \"silent\",\n requestTimeout?: Quantity,\n): Band => {\n const error = failSpan(timing, poll, \"error\");\n if (failure === \"refuses\") return band(s(error.min), s(error.max));\n const wait = requestTimeout === undefined ? s(poll.timeout) : Math.min(s(requestTimeout), s(poll.timeout));\n const late = failSpan(timing, { ...poll, timeout: seconds(wait) }, \"timeout\");\n return band(s(error.min), s(late.max));\n};\n\n/** This function gives the time from a fault to the removal of the endpoints from the rules: the latch, then the kubelet, then the propagation. */\nexport const removalBand = (latch: Band, probe: KubeletProbe, propagation: Propagation): Band => {\n const kubelet = secondsOf(kubeletBand(probe, false));\n const trip = secondsOf(latch);\n const spread = secondsOf(propagation);\n return band(trip.lo + kubelet.lo + spread.lo, trip.hi + kubelet.hi + spread.hi);\n};\n\n/**\n * This function gives the time from a fault to the SIGTERM of a replica whose liveness fails with\n * its readiness. The time is the latch, then the liveness probes of the kubelet, then the preStop sleep.\n */\nexport const restartBand = (latch: Band, liveness: KubeletProbe, preStop: Quantity): Band => {\n const kubelet = secondsOf(kubeletBand(liveness, false));\n const trip = secondsOf(latch);\n return band(trip.lo + kubelet.lo + s(preStop), trip.hi + kubelet.hi + s(preStop));\n};\n\n/**\n * This function gives the time from the start of a stall of the event loop to the removal of the\n * replica from the rules. Each probe in the stall fails at its timeout, because the loop does not answer it.\n */\nexport const stallRemovalBand = (readiness: KubeletProbe, propagation: Propagation): Band => {\n const kubelet = secondsOf(kubeletBand(readiness, true));\n const spread = secondsOf(propagation);\n return band(kubelet.lo + spread.lo, kubelet.hi + spread.hi);\n};\n\n/** This function gives the cap of a stage in a declaration, or zero when the declaration does not name the stage. */\nexport const capOf = (declaration: Declaration, stage: Stage): Quantity => {\n const found = declaration.phases.find(([name]) => name === stage);\n return seconds(found === undefined ? 0 : s(found[1]));\n};\n\n/**\n * This function gives the shortest time from the deletion of a pod to the stop of its intake. It\n * is the preStop sleep, then the hold of `fail readiness`. The kubelet adds its own delay before\n * the preStop, thus the real time is longer.\n */\nexport const intakeAfter = (declaration: Declaration, preStop: Quantity): Quantity =>\n seconds(us(s(preStop) + s(capOf(declaration, \"fail readiness\"))));\n\n/**\n * A deletion window is what a deletion predicts on one node. The listener of the pod closes at the\n * stop of its intake. The rules of the node stop the new connections to the pod at the\n * propagation. A connection between the two is refused. Thus the window is empty when the intake\n * stops after the latest propagation.\n */\nexport interface DeletionWindow {\n /** This flag tells that no connection can meet a closed listener. */\n readonly empty: boolean;\n /** This band is the end of the window after the deletion: the propagation. */\n readonly end: Band;\n /** This time is the earliest stop of the intake after the deletion. */\n readonly intake: Quantity;\n}\n\n/** This function gives the deletion window of a declaration and a preStop sleep on a node with a propagation. */\nexport const deletionWindow = (declaration: Declaration, preStop: Quantity, propagation: Propagation): DeletionWindow => {\n const intake = intakeAfter(declaration, preStop);\n const spread = secondsOf(propagation);\n return { empty: s(intake) >= spread.hi, end: band(spread.lo, spread.hi), intake };\n};\n\n/**\n * This function gives the window on a node whose kube-proxy pauses across a deletion. The pause\n * starts `lead` before the deletion and holds for `hold`. The node then syncs in one propagation\n * after the end of the pause, at `resume` after the deletion. The window is empty when the intake\n * stops after that.\n */\nexport const pausedWindow = (\n declaration: Declaration,\n preStop: Quantity,\n lead: Quantity,\n hold: Quantity,\n propagation: Propagation,\n): DeletionWindow & { readonly resume: Quantity } => {\n const intake = intakeAfter(declaration, preStop);\n const resume = us(s(hold) - s(lead));\n const spread = secondsOf(propagation);\n return { empty: s(intake) >= resume + spread.hi, end: band(spread.lo, spread.hi), intake, resume: seconds(resume) };\n};\n\nexport { showBand } from \"./bands.ts\";\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { Finding, Rule, Scenario, ServiceDescription, Verdict } from \"@mark1russell7/service\";\nimport { lint } from \"@mark1russell7/service\";\nimport { redisClient } from \"@mark1russell7/service/redis\";\nimport type { KindCell } from \"./cell.ts\";\nimport { compose } from \"./compose.ts\";\nimport type { Propagation } from \"./predict.ts\";\n\n/**\n * These are the rules of the ordering law that a drain in Kubernetes meets, as data. The caller of\n * `lint()` gives the rules, thus they are here and not in the service.\n */\n\n/**\n * O04 with the hold of the service. The listener of a pod stays open through the preStop sleep and\n * then through the hold of `fail readiness`. Thus the sum of the two must cover the propagation of\n * the endpoints. The rule of the research is the case without a hold.\n */\nexport const O04: Rule = {\n id: \"O04\",\n rule: \"the preStop sleep plus the hold of fail readiness is at least the endpoint propagation\",\n window: \"new connections reach a pod whose listener is closed\",\n expr: {\n lhs: {\n terms: [\n { knob: \"prestop_sleep\", coef: 1, at: \"pod\" },\n { knob: \"readiness_hold\", coef: 1, at: \"process\" },\n ],\n },\n op: \">=\",\n rhs: { terms: [{ knob: \"propagation_bound\", coef: 1, at: \"cluster\" }] },\n },\n};\n\n/** O09 is W8: readiness detection plus propagation is no longer than liveness detection plus the preStop sleep. */\nexport const O09: Rule = {\n id: \"O09\",\n rule: \"readiness detection plus endpoint propagation is no longer than liveness detection plus the preStop sleep\",\n window: \"traffic reaches a container that its liveness probe kills\",\n expr: {\n lhs: {\n terms: [\n { knob: \"readiness_detection_window\", coef: 1, at: \"pod\" },\n { knob: \"propagation_bound\", coef: 1, at: \"cluster\" },\n ],\n },\n op: \"<=\",\n rhs: {\n terms: [\n { knob: \"liveness_detection_window\", coef: 1, at: \"pod\" },\n { knob: \"prestop_sleep\", coef: 1, at: \"pod\" },\n ],\n },\n },\n};\n\n/** O10: the preStop sleep plus the drain of the process fits in the grace period. */\nexport const O10: Rule = {\n id: \"O10\",\n rule: \"the preStop sleep plus the shutdown of the process fits in the grace period\",\n window: \"work in flight at SIGKILL is cut, and each connection closes at once\",\n expr: {\n lhs: {\n terms: [\n { knob: \"prestop_sleep\", coef: 1, at: \"pod\" },\n { knob: \"process_drain_time\", coef: 1, at: \"process\" },\n ],\n },\n op: \"<=\",\n rhs: { terms: [{ knob: \"termination_grace\", coef: 1, at: \"pod\" }] },\n },\n};\n\n/** O16: the cooperative window is shorter than the drain, which is shorter than the grace less the preStop sleep. */\nexport const O16: Rule = {\n id: \"O16\",\n rule: \"the cooperative window is shorter than the drain, which is shorter than the grace less the preStop sleep\",\n window: \"handlers are cut before they answer, or the drain itself gets SIGKILL\",\n expr: {\n lhs: { terms: [{ knob: \"cooperative_window\", coef: 1, at: \"process\" }] },\n op: \"<\",\n rhs: { terms: [{ knob: \"process_drain_time\", coef: 1, at: \"process\" }] },\n },\n expr_more: [\n {\n lhs: { terms: [{ knob: \"process_drain_time\", coef: 1, at: \"process\" }] },\n op: \"<\",\n rhs: {\n terms: [\n { knob: \"termination_grace\", coef: 1, at: \"pod\" },\n { knob: \"prestop_sleep\", coef: -1, at: \"pod\" },\n ],\n },\n },\n ],\n};\n\n/** These are the rules of the drain that each kind cell gets. */\nexport const DRAIN_RULES: readonly Rule[] = [O04, O09, O10, O16];\n\nconst seconds = (amount: Quantity): number => valueIn(amount, \"s\");\nconst s = (value: number): Quantity => ({ value, unit: \"s\" });\n\n/**\n * This function gives the scenario of a cell for `lint()`: the preStop sleep, the grace, the probe\n * period and the propagation. The probe windows are the period times the threshold of each probe.\n */\nexport const scenarioOf = (cell: KindCell, propagation: Propagation): Scenario => ({\n platform: {\n \"prestop_sleep@pod\": cell.pod.preStop,\n \"termination_grace@pod\": cell.pod.grace,\n \"probe_period@pod\": cell.pod.readiness.period,\n \"propagation_bound@cluster\": propagation.hi,\n },\n probes: {\n readiness: s(seconds(cell.pod.readiness.period) * cell.pod.readiness.threshold),\n liveness: s(seconds(cell.pod.liveness.period) * cell.pod.liveness.threshold),\n },\n});\n\n/** A verdict set is the verdict of each drain rule for one declaration, by the identifier of the rule. */\nexport type Verdicts = Readonly<Record<string, Verdict>>;\n\n/** This function applies the drain rules to the description of a cell, and gives each finding. */\nexport const lintCell = (description: ServiceDescription, cell: KindCell, propagation: Propagation): Finding[] =>\n lint(description, scenarioOf(cell, propagation), DRAIN_RULES);\n\n/** This function gives the verdict of each drain rule, by its identifier. */\nexport const verdictsOf = (findings: readonly Finding[]): Verdicts =>\n Object.fromEntries(findings.map((finding) => [finding.rule, finding.verdict]));\n\n/** This function gives the description of the service of a cell, as the app of the image composes it. Nothing connects. */\nexport const describeCell = (cell: KindCell): ServiceDescription => {\n if (cell.declaration === null) throw new Error(`${cell.name}: the server is not the service, thus the cell has no declaration to describe`);\n const redis = cell.declaration.redis;\n const client =\n redis === null\n ? undefined\n : redisClient(\"redis\", { host: \"redis\", port: 6379, system: \"systems.component.redis\", knobs: redis.knobs, heartbeatMisses: redis.misses });\n return compose(\"lab\", cell.declaration, { redis: client }).describe();\n};\n","import { mkdirSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { StartedTestContainer } from \"testcontainers\";\nimport { imageOf } from \"@mark1russell7/harness\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { KindCell, KindRun, Replica } from \"./cell.ts\";\nimport type { Cluster } from \"./cluster.ts\";\nimport { command, createCluster } from \"./cluster.ts\";\nimport type { Step, Target } from \"./faults.ts\";\nimport { NODE, POD, VERSION, deletes as deletesAPod, planOf } from \"./faults.ts\";\nimport { labImage } from \"./image.ts\";\nimport type { MeshImages } from \"./istio.ts\";\nimport { EDS_EVERY, ISTIO_IMAGES, LDS_EVERY, MESH_IMAGES, followsZtunnel, installMesh, meshKey, pollsListeners, serverCluster } from \"./istio.ts\";\nimport type { KindLab, Line, Stamp } from \"./lab.ts\";\nimport { localPin, openKindLab } from \"./lab.ts\";\nimport { CLIENT, REDIS, REDIS_IMAGE, SERVER, clientObjects, namespaceObject, namespaceOf, redisObjects, serverObjects, stackOf } from \"./manifests.ts\";\nimport type { PodLine, Request } from \"./observe.ts\";\nimport { byClients } from \"./observe.ts\";\nimport type { EnvironmentManifest, KindCellRecord, KindCellResult, LoadSample } from \"./record.ts\";\nimport { FORMAT } from \"./record.ts\";\nimport { scenario } from \"./scenario.ts\";\nimport { judgeKind, observationAfter, observationEnd } from \"./verdict.ts\";\n\n/** This time is the interval of the poll of the rules on each node. */\nexport const RULES_EVERY: Quantity = { value: 50, unit: \"ms\" };\n\n/** These options run a catalog of kind cells. */\nexport interface RunOptions {\n /** This folder gets the raw record of each cell and `summary.ndjson`. */\n readonly out: string;\n readonly log: (line: string) => void;\n /** This count is the number of environments, each a new cluster. */\n readonly environments?: number;\n /** This share of the CPUs of the VM must be idle before a run starts. */\n readonly idle?: number;\n /**\n * This folder holds the run folder of each cluster: its config, its kubeconfig and the cache of\n * kubectl. It is not the output, because a kubeconfig holds credentials.\n */\n readonly work?: string;\n}\n\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\nconst slug = (name: string): string => name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n/** This function tells if a cell has Redis: only a declaration of the service can have it. */\nconst hasRedis = (cell: KindCell): boolean => cell.declaration !== null && cell.declaration.redis !== null;\n\n/** These are the images of a cluster: the lab, Redis, grpc-go for the stack `grpc`, and Istio for a mesh. */\ninterface Images {\n readonly lab: string;\n readonly redis: string;\n readonly grpc?: string | undefined;\n readonly mesh?: MeshImages | undefined;\n}\n\n/** This function gives the key of the cluster of a cell: its kube-proxy mode, its node count, and the flavor of its mesh. */\nconst clusterKey = (cell: KindCell): string => (cell.mesh === undefined ? `${cell.mode}-${cell.workers}` : `${cell.mode}-${cell.workers}-${cell.mesh.flavor}`);\n\n/**\n * This tracker keeps the latest state of the cell from the lines: each pod, each slice and each\n * client. It also has, for each node, the backends of its rules and the hosts of its client sidecar.\n */\nclass Tracker {\n readonly pods = new Map<string, PodLine>();\n readonly slices = new Map<string, Line>();\n readonly backends = new Map<string, readonly string[]>();\n readonly hosts = new Map<string, readonly string[]>();\n readonly clients = new Map<string, { node: string; ok: number; failed: number }>();\n /** This map is the backend of the last answer of each client, by client. */\n readonly answers = new Map<string, string>();\n last = 0;\n see = (line: Line): void => {\n if (typeof line.t === \"number\" && line.t > this.last && line.ev === \"req\") this.last = line.t;\n if (line.ev === \"pod\") {\n const pod = line as unknown as PodLine;\n if (pod.type === \"DELETED\") this.pods.delete(pod.name);\n else this.pods.set(pod.name, pod);\n } else if (line.ev === \"slice\") {\n if (line[\"type\"] === \"DELETED\") this.slices.delete(String(line[\"name\"]));\n else this.slices.set(String(line[\"name\"]), line);\n } else if (line.ev === \"rules\") {\n this.backends.set(String(line[\"node\"]), line[\"b\"] as readonly string[]);\n } else if (line.ev === \"eds\") {\n this.hosts.set(String(line[\"node\"]), line[\"b\"] as readonly string[]);\n } else if (line.ev === \"req\") {\n const request = line as unknown as Request;\n const client = this.clients.get(request.p) ?? { node: request.n, ok: 0, failed: 0 };\n if (request.o === \"ok\") client.ok = Math.max(client.ok, request.t);\n else client.failed = Math.max(client.failed, request.t);\n this.clients.set(request.p, client);\n if (request.o === \"ok\" && request.b !== undefined) this.answers.set(request.p, request.b);\n }\n };\n servers(): PodLine[] {\n return [...this.pods.values()].filter((pod) => pod.labels[\"app\"] === SERVER).sort((a, b) => a.name.localeCompare(b.name));\n }\n}\n\n/** A context is the state of one cell in one environment. */\ninterface Context {\n readonly lab: KindLab;\n readonly cluster: Cluster;\n readonly cell: KindCell;\n readonly namespace: string;\n readonly tracker: Tracker;\n readonly clusterIp: string;\n readonly loads: LoadSample[];\n readonly loader: StartedTestContainer;\n readonly idle: number;\n readonly log: (line: string) => void;\n version: number;\n probed: string;\n /** This key is the server pods whose sidecars the LDS pollers read, or \"\" for none. */\n listened: string;\n /** This count is the replicas of the service at rest: one less while a scale-in holds. */\n expected: number;\n /** This map gives the PID of the app of each pod. A replica gets it from the last fault of the target `servers`, and a client from the last gap. */\n readonly pids: Map<string, number>;\n}\n\n/** This function tells why the cell is not at rest, or gives null when it is. */\nconst unsteady = (ctx: Context): string | null => {\n const { tracker, cell } = ctx;\n const servers = tracker.servers();\n if (servers.length !== ctx.expected) return `${servers.length} replicas, not ${ctx.expected}`;\n const busy = servers.find((pod) => pod.del || !pod.ready);\n if (busy !== undefined) return `${busy.name} is not ready`;\n if (new Set(servers.map((pod) => pod.labels[\"pod-template-hash\"])).size > 1) return \"two versions of the replicas\";\n if (hasRedis(cell)) {\n const redis = [...tracker.pods.values()].filter((pod) => pod.labels[\"app\"] === REDIS);\n if (redis.length !== 1 || !redis[0]!.ready || redis[0]!.del) return \"Redis is not ready\";\n }\n const ips = servers.map((pod) => pod.ip ?? \"\").sort();\n const ready = [...tracker.slices.values()]\n .filter((slice) => slice[\"svc\"] === SERVER)\n .flatMap((slice) => (slice[\"eps\"] as Array<{ ip: string; r: boolean }>).filter((endpoint) => endpoint.r).map((endpoint) => endpoint.ip))\n .sort();\n if (ready.join() !== ips.join()) return `the EndpointSlice has ${ready.join(\" \")}, not ${ips.join(\" \")}`;\n const expected = ips.map((ip) => `${ip}:8080`).sort().join();\n for (const node of ctx.cluster.nodes)\n if ((tracker.backends.get(node) ?? []).join() !== expected) return `the rules of ${node} have ${(tracker.backends.get(node) ?? []).join(\" \")}`;\n // in a sidecar mesh, the client sidecar of each node picks the backend of each call from its endpoints\n if (cell.mesh?.mode === \"sidecar\")\n for (const node of ctx.cluster.nodes)\n if ((tracker.hosts.get(node) ?? []).join() !== expected) return `the client sidecar of ${node} has ${(tracker.hosts.get(node) ?? []).join(\" \") || \"no host\"}`;\n const nodes = new Set([...tracker.clients.values()].map((client) => client.node));\n if (nodes.size !== ctx.cluster.nodes.length) return `clients answer on ${nodes.size} nodes`;\n for (const [pod, client] of tracker.clients) {\n if (tracker.last - client.ok > 1000) return `client ${pod} has no answer in the last second`;\n if (tracker.last - client.failed < 2000) return `client ${pod} failed in the last 2 s`;\n }\n return null;\n};\n\n/** This function waits until the cell is at rest, and gives null, or the reason when it is not at rest in time. */\nconst steady = async (ctx: Context, within: number): Promise<string | null> => {\n const end = Date.now() + within;\n let why = unsteady(ctx);\n while (why !== null && Date.now() < end) {\n await sleep(200);\n why = unsteady(ctx);\n }\n return why;\n};\n\n/** This function samples the load of the VM for 4 s, and names the lab containers that are not of this run. */\nconst sample = async (ctx: Context, ours: ReadonlySet<string>): Promise<LoadSample> => {\n const result = await ctx.loader.exec([\"top\", \"-b\", \"-n\", \"2\", \"-d\", \"4\"]);\n const second = result.stdout.split(/^Mem:/m).at(-1) ?? \"\";\n const idle = Number(/(\\d+)% idle/.exec(second)?.[1] ?? \"0\") / 100;\n const top = second\n .split(\"\\n\")\n .filter((line) => /^\\s*\\d+\\s/.test(line))\n .slice(0, 6)\n .map((line) => line.trim().replace(/\\s+/g, \" \"));\n const listed = (await command(\"docker\", [\"ps\", \"--format\", \"{{.ID}} {{.Names}} {{.Image}}\"])).trim().split(\"\\n\");\n const foreign = listed\n .filter((line) => line !== \"\")\n .filter((line) => !ours.has(line.split(\" \")[1]!) && !ours.has(line.split(\" \")[0]!))\n .filter((line) => /loom-|kindest\\/node|toxiproxy|haproxy|redis|systems-kind-lab|f6-/.test(line.split(\" \")[2] ?? \"\"));\n return { t: Date.now(), idle, top, foreign };\n};\n\n/** This function waits until the VM is quiet: no lab container of another agent, and an idle share of the CPUs. */\nconst quiet = async (ctx: Context, ours: ReadonlySet<string>): Promise<LoadSample> => {\n for (;;) {\n // the prober and the watcher start again with new names, thus the names of this lab come at each sample\n const load = await sample(ctx, new Set([...ours, ...ctx.lab.own(), ctx.loader.getName().replace(/^\\//, \"\")]));\n ctx.loads.push(load);\n if (load.foreign.length === 0 && load.idle >= ctx.idle) return load;\n ctx.log(` waiting: ${Math.round(load.idle * 100)}% idle, other lab containers: ${load.foreign.join(\"; \") || \"none\"}`);\n await sleep(30_000);\n }\n};\n\n/**\n * This function deploys a cell in its namespace, with the watcher, the poll of the rules and the\n * clients. A client of gRPC starts after the server is ready: each failed connect of a channel makes\n * the backoff of grpc-js longer, up to 120 s.\n */\nconst deploy = async (lab: KindLab, cluster: Cluster, cell: KindCell, namespace: string, images: Images): Promise<string> => {\n await cluster.apply([namespaceObject(cell, namespace)]);\n await lab.watch([namespace], { ztunnel: followsZtunnel(cell) });\n if (hasRedis(cell)) {\n await cluster.apply(redisObjects(namespace, images.redis));\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"status\", `deployment/${REDIS}`, \"--timeout=180s\"]);\n }\n const grpc = stackOf(cell) === \"grpc\";\n if (grpc && images.grpc === undefined) throw new Error(`${cell.name}: the cluster has no image of grpc-go`);\n await cluster.apply(serverObjects(cell, namespace, grpc ? images.grpc! : lab.image));\n const clusterIp = (await cluster.kubectl([\"-n\", namespace, \"get\", \"service\", SERVER, \"-o\", \"jsonpath={.spec.clusterIP}\"])).trim();\n const clients = clientObjects(cell, namespace, lab.image, `${clusterIp}:80`);\n if (!grpc) await cluster.apply(clients);\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"status\", `deployment/${SERVER}`, \"--timeout=300s\"]);\n if (grpc) await cluster.apply(clients);\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"status\", `daemonset/${CLIENT}`, \"--timeout=300s\"]);\n await lab.rules({ mode: cluster.scenario.kubeProxy.mode, ns: namespace, svc: SERVER, ip: clusterIp, port: 80, every: RULES_EVERY });\n if (cell.mesh?.mode === \"sidecar\") await pollEndpoints(lab, cluster, namespace);\n return clusterIp;\n};\n\n/**\n * This function starts the EDS poll of the client sidecar on each node: the hosts of the server\n * cluster, each 50 ms. The agent enters the network namespace of the client pod through the PID of\n * its client process. The sidecar shares that namespace.\n */\nconst pollEndpoints = async (lab: KindLab, cluster: Cluster, namespace: string): Promise<void> => {\n const listed = JSON.parse(await cluster.kubectl([\"-n\", namespace, \"get\", \"pods\", \"-l\", `app=${CLIENT}`, \"-o\", \"json\"])) as {\n items: Array<{ metadata: { name: string; deletionTimestamp?: string }; spec: { nodeName?: string } }>;\n };\n const pods: Array<{ node: string; pod: string; pid: number }> = [];\n for (const item of listed.items.filter((each) => each.metadata.deletionTimestamp === undefined)) {\n const node = item.spec.nodeName ?? \"\";\n pods.push({ node, pod: item.metadata.name, pid: await lab.pidOf(node, namespace, item.metadata.name, CLIENT) });\n }\n await lab.eds({ cluster: serverCluster(namespace), every: EDS_EVERY, pods });\n};\n\n/**\n * This function starts the LDS poll of the sidecar of each server pod, each 50 ms, for a cell that\n * polls them (`pollsListeners`). It starts the pollers again only when the server pods changed. The\n * agent enters the network namespace of the pod through the PID of its app.\n */\nconst pollListeners = async (ctx: Context): Promise<void> => {\n if (!pollsListeners(ctx.cell)) return;\n const servers = ctx.tracker.servers().filter((pod) => !pod.del && pod.node !== null);\n const key = servers.map((pod) => `${pod.name}/${pod.cs.map((status) => status.id).join(\"+\")}`).sort().join();\n if (key === ctx.listened) return;\n const pods: Array<{ node: string; pod: string; pid: number }> = [];\n for (const pod of servers) pods.push({ node: pod.node!, pod: pod.name, pid: await ctx.lab.pidOf(pod.node!, ctx.namespace, pod.name, \"app\") });\n await ctx.lab.lds({ every: LDS_EVERY, pods });\n ctx.listened = key;\n};\n\n/** This function starts the prober again when the pods of the cell changed: it watches the namespace of each pod. */\nconst probeThePods = async (ctx: Context): Promise<void> => {\n const pods = [...ctx.tracker.pods.values()].filter((pod) => !pod.del && pod.node !== null && pod.cs.every((status) => status.st === \"running\"));\n const key = pods.map((pod) => `${pod.name}/${pod.cs.map((status) => status.id).join(\"+\")}`).sort().join();\n if (key === ctx.probed) return;\n const netns: number[] = [];\n for (const pod of pods) {\n const container = pod.labels[\"app\"] === SERVER ? \"app\" : pod.labels[\"app\"] === CLIENT ? CLIENT : REDIS;\n const pid = await ctx.lab.pidOf(pod.node!, ctx.namespace, pod.name, container);\n netns.push(await ctx.lab.netnsOf(pod.node!, pid));\n }\n await ctx.lab.probe([...new Set(netns)]);\n ctx.probed = key;\n};\n\n/**\n * This function gives the replicas of the server at this time, in the sequence of their names. The\n * first one is the target of a run. A client of gRPC holds its connection to one replica, and the\n * channels go to the replica that stays after a run. Thus for the stack `grpc`, the first replica\n * is the one that answers the most clients (`byClients`).\n */\nconst replicasOf = (ctx: Context): Replica[] => {\n const replicas = ctx.tracker.servers().map((pod) => ({ pod: pod.name, node: pod.node ?? \"\", ip: pod.ip ?? \"\", version: pod.labels[\"pod-template-hash\"] ?? \"\" }));\n return stackOf(ctx.cell) === \"grpc\" ? byClients(replicas, ctx.tracker.answers) : replicas;\n};\n\n/**\n * This time is how long before the commit of its rules the lab starts the injector in each replica.\n * It is more than the execs of one node before its commit: a read of its tables and the injector.\n */\nconst INJECT_LEAD_MS = 700;\n\n/**\n * This function puts the rules of an action in the network namespace of each replica, with each\n * client pod as a peer: the target `servers`. The rules of all replicas commit at one instant on\n * CLOCK_MONOTONIC (`--at`). Thus no client meets one replica with the rule and one without it. The\n * replicas of one node go one after the other, and the nodes at the same time.\n *\n * The fault finds the PID of each replica, and the clear uses it again: thus the heal comes soon\n * after its hold. Before the commit of a clear, the lab reads the filter table of each replica. Its\n * counters show if the rule matched the clients beside the rules of istio-cni. After the commit of a\n * fault, the lab reads the nat table, with the redirect of istio-cni. The stamp is the first commit\n * and the last end.\n */\nconst injectServers = async (ctx: Context, action: string, replicas: readonly Replica[]): Promise<Stamp> => {\n const clients = [...ctx.tracker.pods.values()]\n .filter((pod) => pod.labels[\"app\"] === CLIENT && !pod.del && pod.ip !== null)\n .map((pod) => pod.ip!)\n .sort();\n if (clients.length === 0 || replicas.length === 0) throw new Error(`the injector needs clients and replicas: ${clients.length} clients, ${replicas.length} replicas`);\n const clear = action.startsWith(\"clear\");\n const byNode = new Map<string, number[]>();\n for (const replica of replicas) {\n const known = clear ? ctx.pids.get(replica.pod) : undefined;\n const pid = known ?? (await ctx.lab.pidOf(replica.node, ctx.namespace, replica.pod, \"app\"));\n ctx.pids.set(replica.pod, pid);\n byNode.set(replica.node, [...(byNode.get(replica.node) ?? []), pid]);\n }\n const at = (await ctx.lab.now()) + INJECT_LEAD_MS;\n const args = [action, clients.join(\",\"), \"--at\", String(Math.round(at * 1e6))];\n const stamps = await Promise.all(\n [...byNode].map(async ([node, pids]) => {\n const out: Stamp[] = [];\n for (const pid of pids) {\n if (clear) await ctx.lab.tables(node, pid, \"filter\");\n out.push(await ctx.lab.inject(node, pid, args));\n }\n return out;\n }),\n );\n if (!clear) for (const [node, pids] of byNode) for (const pid of pids) await ctx.lab.tables(node, pid, \"nat\");\n const all = stamps.flat();\n return { t0: Math.min(...all.map((stamp) => stamp.t0)), t1: Math.max(...all.map((stamp) => stamp.t1)) };\n};\n\n/**\n * This function sends a signal to the process of each client pod: the target `clients`. The nodes go\n * at the same time. SIGUSR1 finds the PID of each client, and SIGUSR2 uses it again, thus the heal of\n * a gap comes soon after its hold. When that PID fails, the client started again, and the lab finds\n * its PID. The stamp is the first act and the last end.\n */\nconst signalClients = async (ctx: Context, signal: string): Promise<Stamp> => {\n const clients = [...ctx.tracker.pods.values()].filter((pod) => pod.labels[\"app\"] === CLIENT && !pod.del && pod.node !== null);\n if (clients.length === 0) throw new Error(`the signal ${signal} needs clients`);\n const stamps = await Promise.all(\n clients.map(async (pod) => {\n const node = pod.node!;\n const known = signal === \"SIGUSR2\" ? ctx.pids.get(pod.name) : undefined;\n if (known !== undefined) {\n try {\n return await ctx.lab.act(node, { op: \"signal\", pid: known, signal });\n } catch {\n // the client started again: its PID is new\n }\n }\n const pid = await ctx.lab.pidOf(node, ctx.namespace, pod.name, CLIENT);\n ctx.pids.set(pod.name, pid);\n return ctx.lab.act(node, { op: \"signal\", pid, signal });\n }),\n );\n return { t0: Math.min(...stamps.map((stamp) => stamp.t0)), t1: Math.max(...stamps.map((stamp) => stamp.t1)) };\n};\n\n/** This function does one step of a fault plan. It gives the stamp of its act, or null for an act without a stamp. */\nconst doStep = async (ctx: Context, step: Step, replicas: readonly Replica[], background: Array<Promise<unknown>>): Promise<Stamp | null> => {\n const { lab, cluster, namespace } = ctx;\n const target = replicas[0]!;\n const fill = (args: readonly string[]): string[] =>\n args.map((arg) =>\n arg.replaceAll(\"{namespace}\", namespace).replaceAll(POD, target.pod).replaceAll(NODE, target.node).replaceAll(VERSION, String(ctx.version)),\n );\n const where = async (which: Target): Promise<{ node: string; pid: number; pod: PodLine | null }> => {\n if (which === \"servers\") throw new Error(\"the target servers is a target of the injector only\");\n if (which === \"clients\") throw new Error(\"the target clients is a target of a signal only\");\n if (which === \"server\") return { node: target.node, pid: await lab.pidOf(target.node, namespace, target.pod, \"app\"), pod: null };\n if (which === \"redis\") {\n const redis = [...ctx.tracker.pods.values()].find((pod) => pod.labels[\"app\"] === REDIS && !pod.del)!;\n return { node: redis.node!, pid: await lab.pidOf(redis.node!, namespace, redis.name, REDIS), pod: redis };\n }\n const worker = cluster.nodes[1] ?? cluster.nodes[0]!;\n const proxy = JSON.parse(\n await cluster.kubectl([\"-n\", \"kube-system\", \"get\", \"pods\", \"-l\", \"k8s-app=kube-proxy\", \"--field-selector\", `spec.nodeName=${worker}`, \"-o\", \"json\"]),\n ) as { items: Array<{ metadata: { name: string } }> };\n return { node: worker, pid: await lab.pidOf(worker, \"kube-system\", proxy.items[0]!.metadata.name, \"kube-proxy\"), pod: null };\n };\n switch (step.do) {\n case \"kubectl\": {\n const t0 = await lab.now();\n if (step.args.includes(`LAB_VERSION=${VERSION}`)) ctx.version += 1;\n const call = cluster.kubectl(fill(step.args));\n if (step.background === true) {\n background.push(call.catch((error: unknown) => ctx.log(` kubectl ${step.args.join(\" \")}: ${String(error)}`)));\n return { t0, t1: t0 };\n }\n await call;\n return { t0, t1: await lab.now() };\n }\n case \"signal\": {\n if (step.target === \"clients\") return signalClients(ctx, step.signal);\n const at = await where(step.target);\n return lab.act(at.node, { op: \"signal\", pid: at.pid, signal: step.signal });\n }\n case \"pause\": {\n const at = await where(step.target);\n const stamp = lab.act(at.node, { op: \"pause\", pid: at.pid, ms: valueIn(step.time, \"ms\") });\n background.push(stamp);\n const now = await lab.now();\n return { t0: now, t1: now };\n }\n case \"inject\": {\n if (step.target === \"servers\") return injectServers(ctx, step.action, replicas);\n const at = await where(step.target);\n let first: Stamp | null = null;\n for (const replica of replicas) {\n const stamp = await lab.inject(at.node, at.pid, [step.action, replica.ip]);\n first ??= stamp;\n }\n return first;\n }\n case \"flag\": {\n const at = await where(step.target);\n return step.on\n ? lab.act(at.node, { op: \"write\", path: `/proc/${at.pid}/root/tmp/lab-fail`, data: \"1\" })\n : lab.act(at.node, { op: \"remove\", path: `/proc/${at.pid}/root/tmp/lab-fail` });\n }\n case \"cpu\": {\n const at = await where(step.target);\n const cgroup = await lab.cgroupOf(at.node, at.pid);\n return lab.act(at.node, { op: \"write\", path: `${cgroup}/cpu.max`, data: step.max });\n }\n case \"stall\": {\n const at = await where(step.target);\n return lab.act(at.node, { op: \"stall\", pid: at.pid, ms: valueIn(step.time, \"ms\") });\n }\n case \"wait\":\n await sleep(valueIn(step.time, \"ms\"));\n return null;\n case \"apply\": {\n // the marks are in the strings of the object: its JSON keeps them as they are\n const object = JSON.parse(fill([JSON.stringify(step.object)])[0]!) as Record<string, unknown>;\n const t0 = await lab.now();\n await cluster.apply([object]);\n return { t0, t1: await lab.now() };\n }\n }\n};\n\n/** This function tells if a run is over: the trigger is done, and the cell is at rest again. */\nconst over = (\n ctx: Context,\n run: { readonly target: Replica | null; readonly replicas: readonly Replica[]; readonly restarts: ReadonlyMap<string, number> },\n lines: readonly Line[],\n): boolean => {\n if (unsteady(ctx) !== null) return false;\n const kind = ctx.cell.trigger.kind;\n if ((kind === \"crash\" || kind === \"liveness-restart\") && run.target !== null) {\n const pod = ctx.tracker.pods.get(run.target.pod);\n const now = pod === undefined ? 0 : pod.cs.reduce((sum, status) => sum + status.rc, 0);\n return now > (run.restarts.get(run.target.pod) ?? 0);\n }\n if (run.target !== null && [\"pod-deletion\", \"node-drain\", \"scale-in\", \"freeze-process\"].includes(kind) && (ctx.cell.template === \"T07\" || ctx.cell.template === \"T15\"))\n return kind === \"scale-in\" || kind === \"node-drain\"\n ? run.replicas.some((replica) => !ctx.tracker.pods.has(replica.pod))\n : !ctx.tracker.pods.has(run.target.pod);\n if (kind === \"rolling-update\" || kind === \"rollout\") return run.replicas.every((replica) => !ctx.tracker.pods.has(replica.pod));\n if (kind === \"app-stall\") return lines.some((line) => line.ev === \"stall\" && line[\"phase\"] === \"end\");\n return true;\n};\n\n/** This function gives the replica that the trigger stopped. For a scale-in or a drain, the controller chooses it. */\nconst stoppedOf = (cell: KindCell, replicas: readonly Replica[], target: Replica | null, lines: readonly Line[]): Replica | null =>\n deletesAPod(cell) ? (replicas.find((replica) => lines.some((line) => line.ev === \"pod\" && line[\"name\"] === replica.pod && line[\"del\"] === true)) ?? target) : target;\n\n/**\n * This function does one run of a cell: at rest, the trigger, the heal, then the observation. The\n * observation lasts until the cell is at rest again, and until each predicted window can close\n * (`observationEnd`).\n */\nconst runOnce = async (ctx: Context, env: number, index: number, warmup: boolean, ours: Set<string>): Promise<KindRun> => {\n const { lab, cell, cluster, namespace } = ctx;\n const plan = planOf(cell);\n const atRest = await steady(ctx, 300_000);\n const empty = (why: string, start: number): KindRun => ({\n env, index, warmup, start, end: start, acts: { fault: null, heal: null, resume: null }, target: null, paused: null, replicas: [], void: why, lines: lab.take(), dropped: {},\n });\n if (atRest !== null) return empty(`the cell was not at rest: ${atRest}`, await lab.now());\n await probeThePods(ctx);\n await pollListeners(ctx);\n await quiet(ctx, ours);\n if ((await steady(ctx, 300_000)) !== null) return empty(\"the cell was not at rest after the wait for a quiet VM\", await lab.now());\n const replicas = replicasOf(ctx);\n const restarts = new Map(ctx.tracker.servers().map((pod) => [pod.name, pod.cs.reduce((sum, status) => sum + status.rc, 0)]));\n const target = hasRedis(cell) && [\"rst\", \"blackhole\", \"freeze-process\", \"peer-gone-without-close\", \"rollout\"].includes(cell.trigger.kind) ? null : replicas[0]!;\n const paused = cell.template === \"T07\" && cell.trigger.kind === \"freeze-process\" ? (cluster.nodes[1] ?? cluster.nodes[0]!) : null;\n // A SYN with no answer before the run keeps its NAT for 120 s (SYN_SENT). A client that takes its\n // port again in the run meets it, and the restore of the last run leaves such SYNs. Each run starts with none\n const cleared = await lab.forgetHandshakes(ctx.clusterIp, 80);\n lab.take();\n for (const key of Object.keys(lab.dropped)) delete lab.dropped[key];\n const start = await lab.now();\n const background: Array<Promise<unknown>> = [];\n let fault: number | null = null;\n let resume: number | null = null;\n if (cell.trigger.kind === \"scale-in\") ctx.expected = cell.pod.replicas - 1;\n const notes: string[] = [];\n const forgotten = Object.entries(cleared).filter(([, count]) => count > 0);\n if (forgotten.length > 0) notes.push(`before the run, the lab deleted SYN_SENT entries of the ClusterIP: ${forgotten.map(([node, count]) => `${count} on ${node}`).join(\", \")}`);\n for (const step of plan.inject) {\n const stamp = await doStep(ctx, step, replicas, background);\n if (stamp !== null && fault === null) fault = stamp.t0;\n }\n let heal: number | null = null;\n if (plan.hold !== null) {\n const now = await lab.now();\n await sleep(Math.max(0, valueIn(plan.hold, \"ms\") - (now - (fault ?? now))));\n for (const step of plan.heal) {\n try {\n const stamp = await doStep(ctx, step, replicas, background);\n if (stamp !== null && heal === null) heal = stamp.t0;\n } catch (error) {\n notes.push(`the heal ${step.do} failed: ${String(error)}`);\n }\n }\n }\n const settled = await Promise.allSettled(background);\n for (const each of settled) {\n if (each.status === \"fulfilled\" && typeof each.value === \"object\" && each.value !== null && \"t1\" in each.value) resume = (each.value as Stamp).t1;\n }\n // the observation: at least 3 s after the last act, until the trigger happened and the cell is at rest again\n const limit = Date.now() + 240_000 + valueIn(cell.pod.grace, \"ms\") + valueIn(cell.pod.preStop, \"ms\");\n await sleep(3000);\n while (Date.now() < limit && !over(ctx, { target, replicas, restarts }, lab.lines)) await sleep(250);\n // then until each predicted window can close and each request that started in it ended (`observationEnd`)\n const until = observationEnd(cell, { start, acts: { fault, heal, resume }, target: stoppedOf(cell, replicas, target, lab.lines), replicas, lines: lab.lines });\n if (until !== null) {\n const wait = Math.min(until - (await lab.now()), limit - Date.now());\n if (wait > 0) await sleep(wait);\n }\n const end = await lab.now();\n const timedOut = Date.now() >= limit;\n for (const step of plan.restore) {\n try {\n await doStep(ctx, step, replicas, background);\n } catch (error) {\n notes.push(`the restore ${step.do} failed: ${String(error)}`);\n }\n }\n ctx.expected = cell.pod.replicas;\n for (const note of notes) ctx.log(` ${note}`);\n const lines = lab.take();\n const stopped = stoppedOf(cell, replicas, target, lines);\n const restarted = lines.some((line) => line.ev === \"pod\" && (line[\"cs\"] as Array<{ rc: number }>).some((status) => status.rc > 0));\n if (restarted) {\n // a container that restarted gets a back-off on its next restart: new pods start without one\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"restart\", `deployment/${SERVER}`]);\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"status\", `deployment/${SERVER}`, \"--timeout=300s\"]);\n }\n if (cell.trigger.kind === \"node-drain\") await rebalance(ctx);\n ctx.log(` ${warmup ? \"warm-up\" : `run ${index}`}: ${sec(end - start)} s${timedOut ? \", not at rest in time\" : \"\"}`);\n return {\n env, index, warmup, start, end, acts: { fault, heal, resume }, target: stopped, paused, replicas, void: null, lines, dropped: { ...lab.dropped }, notes,\n };\n};\n\nconst sec = (ms: number): number => Math.round(ms) / 1000;\n\n/** This function moves a replica back to a node without one after a drain: it deletes one replica of the node with two. */\nconst rebalance = async (ctx: Context): Promise<void> => {\n await steady(ctx, 300_000);\n const servers = ctx.tracker.servers();\n const byNode = new Map<string, string[]>();\n for (const pod of servers) byNode.set(pod.node ?? \"\", [...(byNode.get(pod.node ?? \"\") ?? []), pod.name]);\n const crowded = [...byNode.values()].find((pods) => pods.length > 1);\n if (crowded !== undefined && byNode.size < ctx.cluster.nodes.length - 1)\n await ctx.cluster.kubectl([\"-n\", ctx.namespace, \"delete\", \"pod\", crowded[0]!, \"--wait=true\"]);\n};\n\n/** This function writes the result of a cell: its raw record, and one line of its verdict in `summary.ndjson`. */\nexport const writeResult = (out: string, result: KindCellResult): void => {\n mkdirSync(out, { recursive: true });\n const record: KindCellRecord = { format: FORMAT, ...result };\n writeFileSync(join(out, `${slug(result.cell.name)}.json`), JSON.stringify(record));\n const { verdict, cell } = result;\n writeFileSync(\n join(out, \"summary.ndjson\"),\n `${JSON.stringify({\n id: cell.id,\n name: cell.name,\n mode: cell.mode,\n trigger: cell.trigger.kind,\n prediction: cell.prediction.kind,\n decision: verdict.decision,\n counted: verdict.counted,\n voided: verdict.voided,\n failed: verdict.failed,\n median: verdict.median,\n why: verdict.why,\n })}\\n`,\n { flag: \"a\" },\n );\n};\n\n/**\n * This function does the runs of a catalog of kind cells. The cells of one kube-proxy mode and node\n * count share a cluster. Each group gets new clusters in sequence, one for each environment. Each\n * environment does its share of the runs of each cell, after one warm-up. The function deletes each\n * cluster after its cells.\n */\nexport const runKindCells = async (cells: readonly KindCell[], options: RunOptions): Promise<KindCellResult[]> => {\n const environments = options.environments ?? 2;\n const image = await labImage();\n const redisImage = await localPin(REDIS_IMAGE);\n // the server of the stack grpc: the image of grpc-go of the harness, built from its tag\n const grpcImage = cells.some((cell) => stackOf(cell) === \"grpc\") ? await imageOf({ kind: \"shipped\", name: \"grpc-go\" }) : undefined;\n // Istio: each image of each flavor of the cells pulled once by its digest, and tagged with it, for `kind load`\n const flavors = new Set(cells.flatMap((cell) => (cell.mesh === undefined ? [] : [cell.mesh.flavor])));\n const pinned = new Map<keyof typeof ISTIO_IMAGES, string>();\n for (const name of new Set([...flavors].flatMap((flavor) => MESH_IMAGES[flavor]))) pinned.set(name, await localPin(ISTIO_IMAGES[name]));\n const meshImages: MeshImages | undefined =\n flavors.size === 0 ? undefined : { pilot: pinned.get(\"pilot\")!, proxy: pinned.get(\"proxy\")!, cni: pinned.get(\"cni\"), ztunnel: pinned.get(\"ztunnel\") };\n const images: Images = { lab: image, redis: redisImage, grpc: grpcImage, mesh: meshImages };\n // the cells of one kube-proxy mode, node count and mesh flavor share a cluster\n const groups = new Map<string, KindCell[]>();\n for (const cell of cells) groups.set(clusterKey(cell), [...(groups.get(clusterKey(cell)) ?? []), cell]);\n const results: KindCellResult[] = [];\n for (const [key, group] of groups) {\n const partial = new Map<string, { runs: KindRun[]; manifests: EnvironmentManifest[]; loads: LoadSample[] }>();\n for (let env = 0; env < environments; env += 1) {\n const s = scenario(`lab-${key}-${env}`, group[0]!.mode, group[0]!.workers);\n const dir = join(options.work ?? join(tmpdir(), \"systems-kind-work\"), s.name);\n const mesh = group[0]!.mesh;\n options.log(`cluster ${s.name}: ${s.kubeProxy.mode}, ${s.workers} workers${mesh === undefined ? \"\" : `, Istio ${mesh.flavor}`}`);\n const cluster = await createCluster(s, dir);\n let lab: KindLab | null = null;\n let loader: StartedTestContainer | null = null;\n try {\n await cluster.load(image);\n await cluster.load(redisImage);\n if (grpcImage !== undefined && group.some((cell) => stackOf(cell) === \"grpc\")) await cluster.load(grpcImage);\n if (mesh !== undefined) {\n if (images.mesh === undefined) throw new Error(`${group[0]!.name}: the run has no images of Istio`);\n for (const name of MESH_IMAGES[mesh.flavor]) {\n const each = images.mesh[name];\n if (each === undefined) throw new Error(`${group[0]!.name}: the run has no image ${name} of Istio`);\n await cluster.load(each);\n }\n const began = Date.now();\n await installMesh(cluster, mesh.flavor, images.mesh);\n options.log(` Istio ${mesh.flavor} installed in ${Math.round((Date.now() - began) / 100) / 10} s`);\n }\n lab = await openKindLab(cluster, image);\n const { GenericContainer } = await import(\"testcontainers\");\n const Shared = class extends GenericContainer {\n withPidMode(mode: string): this {\n (this as unknown as { hostConfig: { PidMode?: string } }).hostConfig.PidMode = mode;\n return this;\n }\n };\n loader = await new Shared(image).withPidMode(\"host\").withNetworkMode(\"none\").withCommand([\"sleep\", \"infinity\"]).start();\n const manifest = await cluster.manifest();\n const ours = new Set<string>([...cluster.nodes]);\n for (const cell of group) {\n const namespace = namespaceOf(cell);\n options.log(`${cell.name} (${cell.id}), environment ${env}`);\n const tracker = new Tracker();\n const off = lab.listen(tracker.see);\n const loads: LoadSample[] = [];\n try {\n const clusterIp = await deploy(lab, cluster, cell, namespace, images);\n for (const name of [...lab.own(), loader.getName().replace(/^\\//, \"\")]) ours.add(name);\n const ctx: Context = { lab, cluster, cell, namespace, tracker, clusterIp, loads, loader, idle: options.idle ?? 0.6, log: options.log, version: 1, probed: \"\", listened: \"\", expected: cell.pod.replicas, pids: new Map() };\n const share = Math.ceil(cell.runs / environments) - (env < cell.runs % environments || cell.runs % environments === 0 ? 0 : 1);\n const runs: KindRun[] = [await runOnce(ctx, env, -1, true, ours)];\n for (let index = 0; index < share; index += 1) runs.push(await runOnce(ctx, env, index, false, ours));\n const entry = partial.get(cell.name) ?? { runs: [], manifests: [], loads: [] };\n entry.runs.push(...runs);\n entry.manifests.push({\n env,\n scenario: s,\n cluster: manifest,\n images: {\n lab: image,\n redis: redisImage,\n ...(stackOf(cell) === \"grpc\" ? { grpc: grpcImage! } : {}),\n ...(cell.mesh === undefined ? {} : Object.fromEntries(MESH_IMAGES[cell.mesh.flavor].map((name) => [`istio-${name}`, ISTIO_IMAGES[name]]))),\n },\n });\n entry.loads.push(...loads);\n partial.set(cell.name, entry);\n mkdirSync(join(options.out, \"partial\"), { recursive: true });\n writeFileSync(join(options.out, \"partial\", `${slug(cell.name)}.env${env}.json`), JSON.stringify({ cell, runs, manifest, loads }));\n } finally {\n off();\n await lab.rules(null).catch(() => undefined);\n await lab.eds(null).catch(() => undefined);\n await lab.lds(null).catch(() => undefined);\n await lab.stopProbe();\n await cluster.kubectl([\"delete\", \"namespace\", namespace, \"--wait=true\", \"--timeout=300s\"]).catch((error: unknown) => options.log(` ${String(error)}`));\n }\n }\n } finally {\n if (loader !== null) await loader.stop({ timeout: 0 }).catch(() => undefined);\n if (lab !== null) await lab.close();\n await cluster.delete();\n options.log(`cluster ${s.name} deleted`);\n }\n }\n for (const cell of group) {\n const entry = partial.get(cell.name);\n if (entry === undefined) continue;\n const result: KindCellResult = { cell, ...entry, verdict: judgeKind(cell, entry.runs), at: new Date().toISOString() };\n writeResult(options.out, result);\n rmSync(join(options.out, \"partial\", `${slug(cell.name)}.env0.json`), { force: true });\n for (let env = 1; env < environments; env += 1) rmSync(join(options.out, \"partial\", `${slug(cell.name)}.env${env}.json`), { force: true });\n options.log(`${cell.name}: ${result.verdict.decision}, ${result.verdict.why}`);\n results.push(result);\n }\n }\n return results;\n};\n\n/** This function gives the estimate of a cell in seconds, for the arithmetic of the shards. */\nconst estimate = (cell: KindCell, environments = 2): number => {\n const hold = cell.trigger.hold === undefined ? 0 : valueIn(cell.trigger.hold, \"s\");\n const trigger: Record<string, number> = {\n \"liveness-restart\": 100,\n crash: 30,\n \"app-stall\": hold + 40,\n \"cpu-throttle\": hold + 40,\n rollout: hold + 50,\n \"authorization-policy\": hold + 20,\n idle: hold + 5,\n };\n // a window can need a longer observation: from the end of the hold, until each request that started in it ended\n const after = observationAfter(cell);\n const observed = after === null ? 0 : hold + valueIn(after, \"s\");\n // the band of `ends` too: until each socket can end\n const closes = cell.prediction.kind === \"ends\" && cell.prediction.band !== null ? valueIn(cell.prediction.band.hi, \"s\") + 3 : 0;\n const each = 25 + Math.max(observed, closes, trigger[cell.trigger.kind] ?? (cell.template === \"T18\" ? hold + 35 : 15 + valueIn(cell.pod.preStop, \"s\")));\n // the install of Istio in each cluster takes 1 to 2 min\n const setup = cell.mesh === undefined ? 60 : 180;\n return setup * environments + (cell.runs + environments) * each;\n};\n\n/**\n * This function gives an estimate of the time of a cell: its setup, then each run and each\n * warm-up. A run waits for the cell at rest, samples the load, does the trigger and waits until the\n * cell is at rest again.\n */\nexport const estimatedTime = (cell: KindCell, environments = 2): Quantity => ({ value: estimate(cell, environments), unit: \"s\" });\n\n/**\n * This function gives the share of a shard of a catalog: cells in sequence. Thus the cells of one\n * mode and one mesh flavor stay together and share clusters. Each shard gets an equal share of the\n * estimated time. In one mode, the cells with no mesh come first, in the sequence of the catalog.\n */\nexport const shardOf = <T extends KindCell>(cells: readonly T[], index: number, count: number): T[] => {\n if (!(Number.isInteger(index) && Number.isInteger(count) && count >= 1 && index >= 0 && index < count))\n throw new RangeError(`shard ${index} of ${count} is not a shard`);\n const rank = (cell: KindCell): string => `${cell.mode}/${meshKey(cell) === \"none\" ? \"\" : meshKey(cell)}`;\n const ordered = [...cells].sort((a, b) => (rank(a) === rank(b) ? 0 : rank(a) < rank(b) ? -1 : 1));\n const total = ordered.reduce((sum, cell) => sum + estimate(cell), 0);\n const out: T[] = [];\n let before = 0;\n for (const cell of ordered) {\n const middle = before + estimate(cell) / 2;\n if (Math.min(count - 1, Math.floor((middle / total) * count)) === index) out.push(cell);\n before += estimate(cell);\n }\n return out;\n};\n"],"mappings":";;;;;;;;;;;;;;;AAYA,MAAM,SAAS,QACb;CAAC;CAAG;CAAG;CAAG;AAAC,CAAC,CAAC,KAAK,OAAO,SAAS,IAAI,MAAM,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG;;;;;;;;;AAUxE,MAAa,cAAc,MAA2B,MAAc,SAAoE;CACtI,MAAM,sBAAM,IAAI,IAAY;CAC5B,MAAM,QAAQ,KAAK,MAAM,IAAI;CAC7B,IAAI,SAAS,YAAY;EACvB,MAAM,uBAAO,IAAI,IAAY;EAC7B,KAAK,MAAM,QAAQ,OAAO;GACxB,IAAI,CAAC,KAAK,WAAW,cAAc,KAAK,CAAC,KAAK,SAAS,GAAG,KAAK,GAAG,GAAG,KAAK,IAAI,EAAE,GAAG;GACnF,MAAM,OAAO,qBAAqB,KAAK,IAAI;GAC3C,IAAI,SAAS,MAAM,KAAK,IAAI,KAAK,EAAG;EACtC;EACA,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,QAAQ,sBAAsB,KAAK,IAAI;GAC7C,IAAI,UAAU,QAAQ,CAAC,KAAK,IAAI,MAAM,EAAG,GAAG;GAC5C,MAAM,KAAK,0BAA0B,KAAK,IAAI;GAC9C,IAAI,OAAO,MAAM,IAAI,IAAI,GAAG,EAAG;EACjC;CACF,OAAO,IAAI,SAAS,YAAY;EAE9B,IAAI,SAAS;EACb,MAAM,OAAO,IAAI,KAAK,GAAG,GAAG,KAAK,IAAI;EACrC,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,OAAO,kCAAkC,KAAK,IAAI;GACxD,IAAI,SAAS,MAAM,SAAS,KAAK,EAAE,CAAE,WAAW,UAAU,KAAK,KAAK,EAAE,CAAE,SAAS,IAAI;QAChF,IAAI,kBAAkB,KAAK,IAAI,GAAG,SAAS;GAChD,IAAI,CAAC,QAAQ;GAEb,KAAK,MAAM,SAAS,KAAK,SAAS,0EAA0E,GAC1G,IAAI,MAAM,EAAE,CAAE,WAAW,IAAI,GAAG,IAAI,IAAI,GAAG,MAAM,GAAG,GAAG,MAAM,IAAI;GAEnE,KAAK,MAAM,SAAS,KAAK,SAAS,6DAA6D,GAAG,IAAI,IAAI,GAAG,MAAM,GAAG,GAAG,MAAM,IAAI;GACnI,KAAK,MAAM,SAAS,KAAK,SAAS,+CAA+C,GAAG,IAAI,IAAI,GAAG,MAAM,GAAG,GAAG,MAAM,IAAI;EACvH;CACF,OAAO;EACL,IAAI,SAAS;EACb,KAAK,MAAM,QAAQ,OAAO;GAExB,MAAM,UAAU,kDAAkD,KAAK,IAAI;GAC3E,IAAI,YAAY,MAAM;IACpB,SAAS,QAAQ,OAAO,SAAS,MAAM,QAAQ,EAAG,MAAM,KAAK,MAAM,SAAS,QAAQ,IAAK,EAAE,MAAM,KAAK;IACtG;GACF;GACA,MAAM,OAAO,qDAAqD,KAAK,IAAI;GAC3E,IAAI,UAAU,SAAS,QAAQ,OAAO,KAAK,EAAE,IAAI,GAAG,IAAI,IAAI,GAAG,MAAM,KAAK,EAAG,EAAE,GAAG,SAAS,KAAK,IAAK,EAAE,GAAG;EAC5G;CACF;CACA,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK;AACvB;;;AChEA,MAAa,mBAA6C;CAAC;CAAY;CAAQ;AAAU;;;;;;AAazF,MAAa,cAAmD;CAC9D,QAAQ;EACN,YAAY;EACZ,OAAO;CACT;CACA,QAAQ;EACN,YAAY;EACZ,OAAO;CACT;AACF;;AA4BA,MAAa,YAAY,MAAc,MAAqB,SAAiB,aAAa,YAA0B;CAClH;CACA;CACA;CACA,WAAW;EAAE;EAAM,eAAe;GAAE,OAAO;GAAG,MAAM;EAAI;EAAG,YAAY;GAAE,OAAO;GAAI,MAAM;EAAI;CAAE;CAChG,KAAK;CACL,WAAW;CACX,eAAe;AACjB;AAEA,MAAM,OAAO;;AAGb,MAAa,oBAAoB,MAA8B;CAC7D,MAAM,WAAqB,CAAC;CAC5B,IAAI,CAAC,KAAK,KAAK,EAAE,IAAI,GAAG,SAAS,KAAK,YAAY,EAAE,KAAK,mEAAmE;CAC5H,IAAI,YAAY,EAAE,gBAAgB,KAAA,GAAW,SAAS,KAAK,0CAA0C,EAAE,YAAY;CACnH,IAAI,CAAC,OAAO,UAAU,EAAE,OAAO,KAAK,EAAE,UAAU,KAAK,EAAE,UAAU,GAAG,SAAS,KAAK,GAAG,EAAE,QAAQ,2CAA2C;CAC1I,IAAI,YAAY,EAAE,WAAW,EAAE,aAAa,GAAG,SAAS,KAAK,kBAAkB,EAAE,UAAU,+BAA+B,EAAE,eAAe;CAC3I,OAAO;AACT;;AAGA,MAAa,aAAa,MAA8B,CACtD,GAAG,EAAE,KAAK,iBACV,GAAG,MAAM,KAAK,EAAE,QAAQ,EAAE,QAAQ,IAAI,GAAG,OAAO,GAAG,EAAE,KAAK,SAAS,OAAO,IAAI,KAAK,KAAK,GAAG,CAC7F;;AAGA,MAAa,cAAc,WAA6B;CACtD,MAAM,KAAK,QAAQ,QAAQ,IAAI;CAC/B,IAAI,CAAC,OAAO,UAAU,EAAE,KAAK,KAAK,GAAG,MAAM,IAAI,WAAW,GAAG,GAAG,0CAA0C;CAC1G,OAAO,KAAK,QAAS,IAAI,GAAG,KAAK,IAAK,KAAK,GAAG,GAAG;AACnD;;AAGA,MAAa,kBAAkB,MAC7B;CACE;CACA;CACA,SAAS,EAAE,UAAU;CACrB,GAAG,EAAE,UAAU,KAAK;CACpB,oBAAoB,WAAW,EAAE,UAAU,aAAa;CACxD,iBAAiB,WAAW,EAAE,UAAU,UAAU;CAClD;AACF,CAAC,CAAC,KAAK,IAAI;;AAsBb,MAAa,cAAc,MAAgC;CACzD,MAAM,WAAW,iBAAiB,CAAC;CACnC,IAAI,SAAS,SAAS,GAAG,MAAM,IAAI,MAAM,gBAAgB,EAAE,KAAK,IAAI,SAAS,KAAK,IAAI,GAAG;CACzF,MAAM,QAAQ,YAAY,EAAE,WAAW,CAAE;CACzC,OAAO;EACL,MAAM;EACN,YAAY;EACZ,YAAY;GACV,UAAU;GACV,eAAe,EAAE,UAAU;GAC3B,mBAAmB;GACnB,WAAW,EAAE;GACb,eAAe,EAAE;EACnB;EACA,OAAO,UAAU,CAAC,CAAC,CAAC,KAAK,MAAM,QAAQ;GACrC,MAAM,OAAO,IAAI,kBAAkB;GACnC;GACA,QAAQ,EAAE,oBAAoB,KAAK,MAAM,EAAE,KAAK,SAAS,CAAC,EAAE;EAC9D,EAAE;EACF,sBAAsB,CAAC,eAAe,CAAC,CAAC;CAC1C;AACF;;AAGA,MAAa,aAAa,SAAmC;CAC3D,MAAM,QAAQ,sCAAsC,KAAK,IAAI;CAC7D,IAAI,UAAU,MAAM,MAAM,IAAI,MAAM,qBAAqB,MAAM;CAC/D,MAAM,SAAS,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,MAAM;CAC3C,MAAM,OAAO,OAAO,MAAM,EAAE;CAC5B,IAAI,OAAO,MAAM,UAAU,QAAQ,GAAG,KAAK,OAAO,IAAI,MAAM,IAAI,MAAM,qBAAqB,MAAM;CACjG,MAAM,OAAO,OAAO,QAAQ,KAAK,UAAU,MAAM,MAAM,OAAO,CAAC;CAC/D,MAAM,OAAO,MAAM,KAAK;CACxB,MAAM,QAAQ,KAAK,MAAM,OAAO,IAAI,IAAI;CACxC,OAAO,CAAC,OAAO,QAAQ,OAAO,CAAC;AACjC;;AAGA,MAAa,eAAe,GAAW,MAAuB;CAC5D,MAAM,CAAC,IAAI,MAAM,UAAU,CAAC;CAC5B,MAAM,CAAC,IAAI,MAAM,UAAU,CAAC;CAC5B,OAAO,MAAM,MAAM,MAAM;AAC3B;;;;AC3IA,MAAa,OAAa;CACxB,MAAM;CACN,SAAS;CACT,QAAQ;EACN,aAAa;GACX,KAAK;GACL,QAAQ;EACV;EACA,aAAa;GACX,KAAK;GACL,QAAQ;EACV;EACA,eAAe;GACb,KAAK;GACL,QAAQ;EACV;EACA,gBAAgB;GACd,KAAK;GACL,QAAQ;EACV;CACF;AACF;;AAGA,MAAa,UAAgB;CAC3B,MAAM;CACN,SAAS;CACT,QAAQ;EACN,aAAa;GACX,KAAK;GACL,QAAQ;EACV;EACA,aAAa;GACX,KAAK;GACL,QAAQ;EACV;EACA,eAAe;GACb,KAAK;GACL,QAAQ;EACV;EACA,gBAAgB;GACd,KAAK;GACL,QAAQ;EACV;CACF;AACF;;;;;;AAOA,MAAa,WAAiB;CAC5B,MAAM;CACN,SAAS;CACT,OAAO;CACP,QAAQ;EACN,aAAa;GACX,KAAK;GACL,QAAQ;EACV;EACA,eAAe;GACb,KAAK;GACL,QAAQ;EACV;EACA,gBAAgB;GACd,KAAK;GACL,QAAQ;EACV;CACF;AACF;;;;;;;AAQA,MAAa,YAAY,SAAqB,UAA8B;CAC1E,MAAM,MAAM,WAAW,OAAO;CAC9B,MAAM,QAAQ,MAAc,WAA2B;EACrD,MAAM,QAAQ,IAAI,SAAS,MAAM,OAAO,MAAM;EAC9C,MAAM,MAAM,MAAM,QAAQ,CAAC;EAC3B,OAAO,MAAM,SAAS,GAAG,QAAQ,KAAK,MAAM,SAAS,GAAG,CAAC,CAAC,SAAS,QAAQ;CAC7E;CACA,IAAI,KAAK;CACT,OAAO,KAAK,OAAO,IAAI,QAAQ;EAC7B,MAAM,OAAO,KAAK,IAAI,GAAG;EACzB,IAAI,SAAS,IAAI;EACjB,MAAM,OAAO,OAAO,SAAS,KAAK,KAAK,KAAK,EAAE,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC;EAChE,IAAI,CAAC,OAAO,SAAS,IAAI,GAAG,MAAM,IAAI,MAAM,iBAAiB,KAAK,sBAAsB;EACxF,MAAM,OAAO,OAAO,aAAa,IAAI,KAAK,QAAQ,CAAC;EACnD,MAAM,SAAS,KAAK,KAAK,KAAK,GAAG;EACjC,MAAM,OAAO,WAAW,KAAK,OAAO,GAAG,OAAO,GAAG;EACjD,KAAK,SAAS,OAAO,SAAS,SAAS,KAAK,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,OAAO,OAAO,IAAI,WAAW,IAAI,SAAS,KAAK,KAAK,KAAK,MAAM,IAAI,CAAC;EACtI,MAAM,MAAM,KAAK,KAAK,OAAO,GAAG,IAAI;CACtC;CACA,MAAM,IAAI,MAAM,4BAA4B,OAAO;AACrD;;AAGA,MAAa,oBACX,QAAQ,IAAI,iBAAiB,cAAc,IAAI,IAAI,cAAc,YAAY,GAAG,CAAC;;AAGnF,MAAa,eAAe,WAAmB,QAAQ,UAAU,OAAe,QAAQ,SACtF,GAAG,SAAS,GAAG;;AAGjB,MAAa,UAAU,UAA8B,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK;;AAGpG,MAAa,YAAY,MAAY,MAAc,YAAY,GAAG,QAAgB,YAAY,MAC5F,KAAK,OAAO,SAAS,GAAG,KAAK,KAAK,GAAG,KAAK,QAAQ,GAAG,OAAO,IAAI,WAAW,OAAO,IAAI,GAAG,KAAK,KAAK,QAAQ,KAAK,IAAI;;AAGtH,MAAM,cAAc,MAAc,OAAmB,eAA8B;CACjF,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;CAC5C,MAAM,UAAU,GAAG,KAAK;CACxB,cAAc,SAAS,KAAK;CAC5B,IAAI,YAAY,UAAU,SAAS,GAAK;CACxC,WAAW,SAAS,IAAI;AAC1B;;AAGA,MAAa,iBAAoC,CAAC,KAAO,GAAK;;;;;AAM9D,MAAa,WAAW,OAAO,KAAa,QAA2B,mBAAwC;CAC7G,KAAK,IAAI,UAAU,IAAK,WAAW;EACjC,MAAM,WAAW,MAAM,MAAM,KAAK,EAAE,UAAU,SAAS,CAAC,CAAC,CAAC,OAAO,UAAoB,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAE;EAC/I,IAAI,oBAAoB,YAAY,SAAS,IAAI,OAAO,IAAI,WAAW,MAAM,SAAS,YAAY,CAAC;EACnG,MAAM,UAAU,oBAAoB,2BAAW,IAAI,MAAM,mBAAmB,IAAI,YAAY,SAAS,QAAQ,IAAI;EACjH,MAAM,OAAO,MAAM;EACnB,IAAK,oBAAoB,YAAY,SAAS,SAAS,OAAQ,SAAS,KAAA,GAAW,MAAM;EACzF,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,IAAI,CAAC;CAC1D;AACF;;;;;;;AAQA,MAAa,aAAa,OAAO,MAAY,MAAc,YAAY,GAAG,QAAgB,YAAY,MAAuB;CAC3H,MAAM,QAAQ,KAAK,OAAO;CAC1B,IAAI,UAAU,KAAA,GAAW,MAAM,IAAI,MAAM,GAAG,KAAK,KAAK,GAAG,KAAK,QAAQ,2BAA2B,KAAK;CACtG,MAAM,OAAO,SAAS,MAAM,KAAK,KAAK;CACtC,MAAM,aAAa,CAAC,IAAI,WAAW,OAAO;CAE1C,MAAM,OAAO,KAAK,UAAU,KAAA,IAAY,OAAO,GAAG,KAAK;CACvD,IAAI;CACJ,IAAI,WAAW,IAAI,GAAG;EACpB,QAAQ,aAAa,IAAI;EACzB,MAAM,QAAQ,OAAO,KAAK;EAC1B,IAAI,UAAU,MAAM,QAAQ,MAAM,IAAI,MAAM,GAAG,KAAK,kBAAkB,MAAM,mBAAmB,MAAM,QAAQ;EAC7G,IAAI,KAAK,UAAU,KAAA,GAAW,OAAO;CACvC,OAAO;EACL,QAAQ,MAAM,SAAS,MAAM,GAAG;EAChC,MAAM,QAAQ,OAAO,KAAK;EAC1B,IAAI,UAAU,MAAM,QAAQ,MAAM,IAAI,MAAM,GAAG,MAAM,IAAI,kBAAkB,MAAM,mBAAmB,MAAM,QAAQ;EAClH,WAAW,MAAM,OAAO,cAAc,KAAK,UAAU,KAAA,CAAS;EAC9D,IAAI,KAAK,UAAU,KAAA,GAAW,OAAO;CACvC;CACA,MAAM,SAAS,SAAS,OAAO,KAAK,KAAK;CACzC,IAAI,CAAC,WAAW,IAAI,KAAK,OAAO,aAAa,IAAI,CAAC,MAAM,OAAO,MAAM,GAAG,WAAW,MAAM,QAAQ,UAAU;CAC3G,OAAO;AACT;;;AC1LA,MAAM,MAAM,UAAU,QAAQ;;AAG9B,MAAa,UAAU,OACrB,SACA,MACA,UAAmH,CAAC,MAChG;CACpB,IAAI;EACF,MAAM,QAAQ,IAAI,SAAS,CAAC,GAAG,IAAI,GAAG;GACpC,KAAK;IAAE,GAAG,QAAQ;IAAK,GAAG,QAAQ;GAAI;GACtC,WAAW,OAAO;GAClB,SAAS,QAAQ,WAAW;GAC5B,aAAa;EACf,CAAC;EACD,IAAI,QAAQ,UAAU,KAAA,GACpB,MAAM,MAAM,OAAO,IAAI,QAAQ,KAAK;EAEtC,MAAM,EAAE,WAAW,MAAM;EACzB,OAAO,OAAO,SAAS;CACzB,SAAS,OAAO;EACd,MAAM,SAAS;EACf,MAAM,IAAI,MAAM,GAAG,QAAQ,GAAG,KAAK,KAAK,GAAG,EAAE,YAAY,OAAO,UAAU,OAAO,WAAW,GAAA,CAAI,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,GAAI,GAAG;CACpI;AACF;;AA8BA,MAAM,gBAAgB,YAA8C;CAClE,MAAM,OAAO,MAAM,QAAQ,UAAU;EAAC;EAAW;EAAM;CAAI,CAAC,EAAA,CAAG,MAAM,KAAK,CAAC,CAAC,QAAQ,OAAO,OAAO,EAAE;CACpG,IAAI,IAAI,WAAW,GAAG,OAAO,CAAC;CAK9B,OAJa,KAAK,MAAM,MAAM,QAAQ,UAAU;EAAC;EAAW;EAAW,GAAG;CAAG,CAAC,CAIpE,CAAC,CAAC,SAAS,aAClB,QAAQ,MAAM,UAAU,CAAC,EAAA,CACvB,KAAK,WAAW,OAAO,UAAU,EAAE,CAAC,CACpC,QAAQ,WAAW,4BAA4B,KAAK,MAAM,CAAC,CAAC,CAC5D,KAAK,WAA6B,CAAC,QAAQ,MAAM,MAAM,CAAC,CAC7D;AACF;;;;;;;AAQA,MAAa,gBAAgB,OAAO,UAAwB,QAAkC;CAC5F,MAAM,OAAO,MAAM,WAAW,IAAI;CAClC,MAAM,cAAc,MAAM,WAAW,OAAO;CAC5C,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;CAClC,MAAM,aAAa,KAAK,KAAK,YAAY;CACzC,MAAM,UAAU,WAAW,SAAS;CACpC,MAAM,SAAS,WAAW,QAAQ;CAClC,KAAK,MAAM,CAAC,MAAM,WAAW,MAAM,cAAc,GAAG;EAClD,IAAI,SAAS,SAAS;EACtB,KAAK,MAAM,QAAQ,CAAC,SAAS,WAAW,SAAS,aAAa,GAC5D,IAAI,YAAY,MAAM,MAAM,GAAG,MAAM,IAAI,MAAM,cAAc,KAAK,+BAA+B,KAAK,IAAI,OAAO,EAAE;CACvH;CACA,MAAM,OAAO,KAAK,KAAK,WAAW;CAClC,cAAc,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,GAAG;CAC1D,MAAM,MAAM,EAAE,kCAAkC,QAAQ;CACxD,MAAM,QAAQ,UAAU,QAAQ;CAChC,MAAM,WAAW,MAAyB,UACxC,QAAQ,aAAa;EAAC;EAAgB;EAAY;EAAe,KAAK,KAAK,eAAe;EAAG,GAAG;CAAI,GAAG,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM,CAAC;CAC7I,MAAM,UAAmB;EACvB;EACA;EACA;EACA;EACA;EACA;EACA,MAAM,MAAM,SAAS;GACnB,MAAM,QAAQ;IAAC;IAAS;IAAM;GAAG,GAAG,KAAK,UAAU;IAAE,YAAY;IAAM,MAAM;IAAQ,OAAO;GAAQ,CAAC,CAAC;EACxG;EACA,OAAO,MAAM,SAAS,QAAQ,UAAU;GAAC;GAAQ;GAAM,GAAG;EAAI,CAAC;EAC/D,MAAM,KAAK,OAAO;GAChB,MAAM,QAAQ,MAAM;IAAC;IAAQ;IAAgB;IAAU,SAAS;IAAM;GAAK,GAAG;IAAE;IAAK,SAAS;GAAQ,CAAC;EACzG;EACA,MAAM,cAAc;GAClB,MAAM,WAAW,KAAK,KAAK,qBAAqB;GAChD,cAAc,UAAU,MAAM,QAAQ,MAAM;IAAC;IAAO;IAAc;IAAc;IAAU,SAAS;GAAI,GAAG,EAAE,IAAI,CAAC,CAAC;GAClH,MAAM,OAAO,KAAK,MAChB,MAAM,QAAQ,aAAa;IAAC;IAAgB;IAAU;IAAU;IAAQ;IAAS;IAAM;GAAM,CAAC,CAChG;GAIA,OAAO;IACL,QAAQ,KAAK,SAAS,EAAE,CAAE,QAAQ;IAClC,IAAI,KAAK,SAAS,EAAE,CAAE,QAAQ;IAC9B,MAAM,KAAK,MAAM,EAAE,CAAE,KAAK;IAC1B,KAAK,KAAK,MAAM,EAAE,CAAE,KAAK;GAC3B;EACF;EACA,MAAM,WAAW;GACf,MAAM,YAAY,KAAK,MAAM,MAAM,QAAQ;IAAC;IAAO;IAAS;IAAM;GAAM,CAAC,CAAC;GAG1E,MAAM,QAAQ,KAAK,MAAM,MAAM,QAAQ;IAAC;IAAM;IAAe;IAAO;IAAa;IAAc;IAAM;GAAM,CAAC,CAAC;GAG7G,MAAM,OAAO,kBAAkB,KAAK,MAAM,KAAK,kBAAkB,EAAE,CAAC,GAAG,MAAM;GAC7E,MAAM,UAAU,KAAK,MAAM,MAAM,QAAQ;IAAC;IAAW;IAAM;GAAM,CAAC,CAAC;GACnE,OAAO;IACL,OAAO,MAAM,QAAQ,MAAM,CAAC,SAAS,CAAC,EAAA,CAAG,KAAK;IAC9C,WAAW,YAAY,SAAS,WAAW,CAAE;IAC7C,QAAQ,QAAQ,eAAe,cAAc;IAC7C,MAAM,KAAK,WAAW,MAAK,EAAE;IAC7B,SAAS,MAAM,QAAQ,UAAU;KAAC;KAAW;KAAY;IAAqB,CAAC,EAAA,CAAG,KAAK;IACvF,OAAO,UAAU,MAAM,KAAK,UAAU;KACpC,MAAM,KAAK,SAAS;KACpB,SAAS,KAAK,OAAO,SAAS,qBAAqB;KACnD,QAAQ,KAAK,OAAO,SAAS,oBAAoB;KACjD,SAAS,KAAK,OAAO,SAAS,8BAA8B;KAC5D,IAAI,KAAK,OAAO,SAAS,cAAc;IACzC,EAAE;GACJ;EACF;EACA,MAAM,SAAS;GACb,MAAM,QAAQ,MAAM;IAAC;IAAU;IAAW;IAAU,SAAS;IAAM;IAAgB;GAAU,GAAG,EAAE,IAAI,CAAC;GACvG,MAAM,QAAQ,UAAU;IAAC;IAAW;IAAM;GAAO,CAAC,CAAC,CAAC,YAAY,KAAA,CAAS;EAC3E;CACF;CACA,MAAM,QACJ,MACA;EAAC;EAAU;EAAW;EAAU,SAAS;EAAM;EAAY;EAAM;EAAgB;EAAY;EAAU;CAAM,GAC7G;EAAE;EAAK,SAAS;CAAQ,CAC1B;CACA,OAAO;AACT;;;;ACtJA,MAAa,aAAa;AAC1B,MAAa,QAAQ;;AAErB,MAAa,aAAa;;AAE1B,MAAa,WAAW;;;;;;AAOxB,MAAa,WAAW,MAAc,aAA0B,QAAe,CAAC,MAAsB;CACpG,MAAM,MAAM,QAAQ,IAAI,CAAC,CACtB,UAAU,YAAY,SAAS,CAAC,CAChC,SAAS,YAAY,QAAQ,CAAC,CAC9B,QAAQ,YAAY,OAAO,CAAC,CAC5B,kBAAkB,YAAY,iBAAiB,CAAC,CAEhD,eAAe,QAAQ;CAC1B,KAAK,MAAM,YAAY,YAAY,WAAW,IAAI,OAAO,QAAQ;CACjE,IAAI,YAAY,UAAU,MAAM;EAC9B,IAAI,MAAM,UAAU,KAAA,GAAW,MAAM,IAAI,MAAM,GAAG,KAAK,uDAAuD;EAC9G,MAAM,MAAM,OAAO,KAAK;GAAE,MAAM,YAAY;GAAM,OAAO,EAAE,cAAc,YAAY,MAAM,MAAM;EAAE,CAAC;CACtG;CACA,IAAI,YAAY,SAAS,MACvB,IAAI,MACF;EACE,MAAM;EACN,YAAY;EACZ,MAAM;EACN,eAAe;EACf,OAAO,YAAY;EACnB,MAAM,YAAY;CACpB,GACA,MAAM,IACR;CACF,IAAI,YAAY,aAAa,MAC3B,IAAI,SAAS;EACX,MAAM;EACN,MAAM,YAAY,SAAS;EAC3B,WAAW,YAAY,SAAS;EAChC,OAAO;EACP,MAAM,YAAY;CACpB,CAAC;CACH,IAAI,OAAO,QAAQ,CAAC,GAAG,MAAM,IAAI;CACjC,MAAM,aAAa,IAAI,WAAW,UAAU;CAC5C,IAAI,YAAY,UAAU,MAAM,WAAW,SAAS,MAAM,MAAO,IAAI;CACrE,WAAW,MAAM,OAAO,EAAE,YAAY,KAAK,CAAC;CAC5C,IAAI,MAAM,WAAW,KAAA,GAAW,IAAI,aAAa,MAAM,MAAM;CAC7D,KAAK,MAAM,CAAC,OAAO,QAAQ,YAAY,QAAQ,IAAI,MAAM,OAAO,GAAG;CACnE,OAAO;AACT;;;;;;;ACrDA,MAAa,mBAAmB,SAAiB,WAAmB,SAAyB,YAAY,KAAK,IAAI,QAAQ,GAAG,UAAU;;AAGvI,MAAa,iBAAiB,YAA4B,IAAI,QAAQ,QAAQ,wBAAwB,MAAM,KAAK,GAAG,EAAE;;;;;;AAOtH,MAAa,WAAW,MAAc,YAAkC;CACtE,MAAM,UAAoB,CAAC;CAC3B,MAAM,QAAgC,CAAC;CACvC,MAAM,SAAS,GAAG,QAAQ;CAC1B,KAAK,MAAM,QAAQ,KAAK,MAAM,IAAI,GAAG;EACnC,IAAI,CAAC,KAAK,WAAW,MAAM,GAAG;EAC9B,MAAM,QAAQ,KAAK,MAAM,OAAO,MAAM,CAAC,CAAC,MAAM,IAAI;EAClD,IAAI,MAAM,WAAW,KAAK,MAAM,OAAO,gBAAgB;EACvD,MAAM,CAAC,QAAQ,SAAS;EACxB,IAAI,MAAM,KAAK,MAAM,WAAW,QAAQ,KAAK,IAAI;OAC5C,MAAM,QAAQ,MAAM,KAAK;CAChC;CACA,OAAO;EAAE,GAAG,QAAQ,KAAK;EAAG,GAAG;CAAM;AACvC;;;ACqBA,MAAM,SAAS,MAA4B,SAA2B;CACpE,IAAI,SAAS,KAAA,GAAW,MAAM,IAAI,MAAM,qBAAqB,MAAM;CACnE,OAAO;AACT;;AAGA,MAAM,UAAU,WAA6B,KAAK,MAAM,QAAQ,QAAQ,IAAI,CAAC;;AAG7E,MAAa,MAAM;;AAEnB,MAAa,OAAO;;AAEpB,MAAa,UAAU;;AAEvB,MAAa,gBAAgB;;AAE7B,MAAa,SAAS;;AAEtB,MAAa,iBAAiB;;;;;;;AAQ9B,MAAa,gBAAgB,aAAwD;CACnF,YAAY;CACZ,MAAM;CACN,UAAU;EAAE,MAAM;EAAQ,WAAW;CAAc;CACnD,MAAM;EACJ,UAAU,EAAE,aAAa,EAAE,KAAA,MAAY,EAAE;EACzC,QAAQ;EACR,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,YAAY,CAAC,mCAAmC,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC;CAChG;AACF;;;;;;AAOA,MAAM,UAAU,SAA4B,KAAK,aAAa,QAAQ,YAAY;;AAGlF,MAAa,UAAU,SAA8B;CACnD,MAAM,KAAK,CAAC,MAAM,aAAa;CAC/B,MAAM,UAAU,KAAK;CACrB,MAAM,WAAW,KAAK,IAAI;CAC1B,QAAQ,QAAQ,MAAhB;EACE,KAAK,gBACH,OAAO;GAAE,QAAQ,CAAC;IAAE,IAAI;IAAW,MAAM;KAAC,GAAG;KAAI;KAAU;KAAO;KAAK;IAAc;GAAE,CAAC;GAAG,MAAM;GAAM,MAAM,CAAC;GAAG,SAAS,CAAC;EAAE;EAC/H,KAAK,kBACH,OAAO;GACL,QAAQ,CAAC;IAAE,IAAI;IAAW,MAAM;KAAC,GAAG;KAAI;KAAO;KAAO;KAAwB,eAAe;IAAS;GAAE,CAAC;GACzG,MAAM;GACN,MAAM,CAAC;GACP,SAAS,CAAC;EACZ;EACF,KAAK,cACH,OAAO;GACL,QAAQ,CACN;IACE,IAAI;IACJ,MAAM;KAAC;KAAS;KAAM;KAAuB;KAA0B;KAAkB;IAA8B;IACvH,YAAY;GACd,CACF;GACA,MAAM;GACN,MAAM,CAAC;GACP,SAAS,CAAC;IAAE,IAAI;IAAW,MAAM,CAAC,YAAY,IAAI;GAAE,CAAC;EACvD;EACF,KAAK,YACH,OAAO;GACL,QAAQ,CAKN,GAAI,KAAK,UAAU,SAAS,CAAC;IAAE,IAAI;IAAoB,MAAM;KAAC,GAAG;KAAI;KAAY;KAAO;KAAK,GAAG,cAAc;KAAM;IAAa;GAAE,CAAC,IAAI,CAAC,GACzI;IAAE,IAAI;IAAW,MAAM;KAAC,GAAG;KAAI;KAAS;KAAwB,cAAc,WAAW;IAAG;GAAE,CAChG;GACA,MAAM;GACN,MAAM,CAAC;GACP,SAAS,CAAC;IAAE,IAAI;IAAW,MAAM;KAAC,GAAG;KAAI;KAAS;KAAwB,cAAc;IAAU;GAAE,CAAC;EACvG;EACF,KAAK,SACH,OAAO;GAAE,QAAQ,CAAC;IAAE,IAAI;IAAU,QAAQ;IAAU,QAAQ;GAAU,CAAC;GAAG,MAAM;GAAM,MAAM,CAAC;GAAG,SAAS,CAAC;EAAE;EAC9G,KAAK,oBAEH,OAAO;GAAE,QAAQ,CAAC;IAAE,IAAI;IAAQ,QAAQ;IAAU,IAAI;GAAK,CAAC;GAAG,MAAM;GAAM,MAAM,CAAC;GAAG,SAAS,CAAC;EAAE;EACnG,KAAK;GACH,IAAI,KAAK,aAAa,OACpB,OAAO;IACL,QAAQ;KACN;MAAE,IAAI;MAAS,QAAQ;MAAc,MAAM,MAAM,QAAQ,MAAM,QAAQ;KAAE;KACzE;MAAE,IAAI;MAAQ,MAAM,MAAM,QAAQ,MAAM,QAAQ;KAAE;KAClD;MAAE,IAAI;MAAW,MAAM;OAAC,GAAG;OAAI;OAAU;OAAO;OAAK;MAAc;KAAE;IACvE;IACA,MAAM;IACN,MAAM,CAAC;IACP,SAAS,CAAC;GACZ;GAEF,OAAO;IACL,QAAQ,CAAC;KAAE,IAAI;KAAU,QAAQ;KAAS,QAAQ;IAAU,CAAC;IAC7D,MAAM,MAAM,QAAQ,MAAM,QAAQ;IAClC,MAAM,CAAC;KAAE,IAAI;KAAU,QAAQ;KAAS,QAAQ;IAAU,CAAC;IAC3D,SAAS,CAAC;GACZ;EACF,KAAK,OACH,OAAO;GACL,QAAQ,CAAC;IAAE,IAAI;IAAU,QAAQ,OAAO,IAAI;IAAG,QAAQ;GAAM,CAAC;GAC9D,MAAM,MAAM,QAAQ,MAAM,QAAQ;GAClC,MAAM,CAAC;IAAE,IAAI;IAAU,QAAQ,OAAO,IAAI;IAAG,QAAQ;GAAY,CAAC;GAClE,SAAS,CAAC;EACZ;EACF,KAAK,aACH,OAAO;GACL,QAAQ,CAAC;IAAE,IAAI;IAAU,QAAQ,OAAO,IAAI;IAAG,QAAQ;GAAY,CAAC;GACpE,MAAM,MAAM,QAAQ,MAAM,QAAQ;GAClC,MAAM,CAAC;IAAE,IAAI;IAAU,QAAQ,OAAO,IAAI;IAAG,QAAQ;GAAQ,CAAC;GAC9D,SAAS,CAAC;EACZ;EACF,KAAK,2BAEH,OAAO;GACL,QAAQ,CACN;IAAE,IAAI;IAAU,QAAQ;IAAS,QAAQ;GAAY,GACrD;IAAE,IAAI;IAAW,MAAM;KAAC,GAAG;KAAI;KAAS,cAAc;KAAS;IAAc;GAAE,CACjF;GACA,MAAM,MAAM,QAAQ,MAAM,QAAQ;GAClC,MAAM,CAAC;IAAE,IAAI;IAAW,MAAM;KAAC,GAAG;KAAI;KAAS,cAAc;KAAS;IAAc;GAAE,CAAC;GACvF,SAAS,CAAC;EACZ;EACF,KAAK,aACH,OAAO;GAAE,QAAQ,CAAC;IAAE,IAAI;IAAS,QAAQ;IAAU,MAAM,MAAM,QAAQ,MAAM,QAAQ;GAAE,CAAC;GAAG,MAAM;GAAM,MAAM,CAAC;GAAG,SAAS,CAAC;EAAE;EAC/H,KAAK,WAEH,OAAO;GACL,QAAQ,CACN;IAAE,IAAI;IAAW,MAAM;KAAC,GAAG;KAAI;KAAS,cAAc;KAAS;IAAc;GAAE,GAC/E;IAAE,IAAI;IAAW,MAAM;KAAC,GAAG;KAAI;KAAO;KAAO;KAAwB,eAAe;IAAS;GAAE,CACjG;GACA,MAAM,MAAM,QAAQ,MAAM,QAAQ;GAClC,MAAM,CAAC;IAAE,IAAI;IAAW,MAAM;KAAC,GAAG;KAAI;KAAS,cAAc;KAAS;IAAc;GAAE,CAAC;GACvF,SAAS,CAAC;EACZ;EACF,KAAK,gBAAgB;GACnB,MAAM,MAAM,QAAQ;GACpB,IAAI,QAAQ,KAAA,GAAW,MAAM,IAAI,MAAM,+BAA+B;GAEtE,OAAO;IACL,QAAQ,CAAC;KAAE,IAAI;KAAO,QAAQ;KAAU,KAAK,GAAG,OAAO,IAAI,KAAK,EAAE,GAAG,OAAO,IAAI,MAAM;IAAI,CAAC;IAC3F,MAAM,MAAM,QAAQ,MAAM,QAAQ;IAClC,MAAM,CAAC;KAAE,IAAI;KAAO,QAAQ;KAAU,KAAK,OAAO,OAAO,IAAI,MAAM;IAAI,CAAC;IACxE,SAAS,CAAC;GACZ;EACF;EACA,KAAK,wBAEH,OAAO;GACL,QAAQ,CAAC;IAAE,IAAI;IAAS,QAAQ,aAAa,QAAQ,WAAA,SAAyB;GAAE,CAAC;GACjF,MAAM,MAAM,QAAQ,MAAM,QAAQ;GAClC,MAAM,CAAC;IAAE,IAAI;IAAW,MAAM;KAAC,GAAG;KAAI;KAAU;KAAuB;IAAM;GAAE,CAAC;GAChF,SAAS,CAAC;IAAE,IAAI;IAAW,MAAM;KAAC,GAAG;KAAI;KAAU;KAAuB;KAAQ;IAAoB;GAAE,CAAC;EAC3G;EACF,KAAK,QAEH,OAAO;GACL,QAAQ,CAAC;IAAE,IAAI;IAAU,QAAQ;IAAW,QAAQ;GAAU,CAAC;GAC/D,MAAM,MAAM,QAAQ,MAAM,6BAA6B;GACvD,MAAM,CAAC;IAAE,IAAI;IAAU,QAAQ;IAAW,QAAQ;GAAU,CAAC;GAC7D,SAAS,CAAC;IAAE,IAAI;IAAU,QAAQ;IAAW,QAAQ;GAAU,CAAC;EAClE;CACJ;AACF;;AAGA,MAAa,WAAW,SACtB;CAAC;CAAgB;CAAkB;CAAc;AAAU,CAAC,CAAC,SAAS,KAAK,QAAQ,IAAI,KACtF,KAAK,aAAa,SAAS,KAAK,QAAQ,SAAS;;;;ACzOpD,MAAM,UAAU,cAAc,IAAI,IAAI,OAAO,YAAY,GAAG,CAAC;;AAE7D,MAAM,OAAO,cAAc,IAAI,IAAI,aAAa,YAAY,GAAG,CAAC;;AAEhE,MAAa,qBAAkE;CAAC;CAAY;CAAW;AAAS;;AAEhH,MAAM,QAAkD;CACtD,CAAC,cAAc,kBAAkB;CACjC,CAAC,gBAAgB,oBAAoB;CACrC,CAAC,iBAAiB,qBAAqB;CACvC,CAAC,WAAW,eAAe;CAC3B,CAAC,cAAc,kBAAkB;CACjC,CAAC,mBAAmB,uBAAuB;CAC3C,CAAC,aAAa,iBAAiB;CAC/B,CAAC,aAAa,iBAAiB;CAC/B,CAAC,cAAc,gBAAgB;CAC/B,CAAC,eAAe,iBAAiB;CACjC,CAAC,gBAAgB,kBAAkB;CACnC,CAAC,YAAY,cAAc;CAC3B,CAAC,UAAU,YAAY;CACvB,CAAC,UAAU,YAAY;CACvB,CAAC,mBAAmB,qBAAqB;AAC3C;AAEA,MAAM,QAAQ,QAAQ,aAAa;;AAGnC,MAAM,WAAW,MAAc,KAAK,OAClC,YAAY,KAAK,MAAM,EAAE,GAAG,EAAE,eAAe,KAAK,CAAC,CAAC,CACjD,SAAS,UAAW,MAAM,YAAY,IAAI,QAAQ,MAAM,KAAK,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,CAAC,CAAE,CAAC,CACxG,KAAK;;AAGV,MAAa,cAAc,MAAc,SAAyB;CAChE,MAAM,OAAO,WAAW,QAAQ;CAChC,KAAK,MAAM,QAAQ,QAAQ,IAAI,GAAG;EAChC,KAAK,OAAO,KAAK,WAAW,MAAM,GAAG,CAAC;EACtC,KAAK,OAAO,aAAa,KAAK,MAAM,IAAI,CAAC,CAAC;CAC5C;CACA,OAAO,GAAG,KAAK,GAAG,KAAK,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,EAAE;AAClD;;AAGA,MAAM,oBACJ,WAAW,KAAK,MAAM,qBAAqB,CAAC,KAAK,mBAAmB,OAAO,SAAS,WAAW,KAAK,MAAM,YAAY,MAAM,cAAc,CAAC,CAAC;;;;;;AAO9I,MAAM,yBAA8C;CAClD,MAAM,0BAAU,IAAI,IAAoB;CACxC,KAAK,MAAM,QAAQ,CAAC,GAAG,kBAAkB,CAAC,CAAC,QAAQ,GAAG;EACpD,MAAM,QAAQ,CAAC,SAAS,GAAG,QAAQ,OAAO,CAAC,CAAC,CAAC,SAAS,SAAS;GAC7D,IAAI;IACF,OAAO,CAAC,QAAQ,aAAa,cAAc,KAAK,MAAM,cAAc,CAAC,CAAC,CAAC,QAAQ,kBAAkB,KAAK,cAAc,CAAC,CAAC,CAAC;GACzH,QAAQ;IACN,OAAO,CAAC;GACV;EACF,CAAC,CAAC,CAAC;EACH,IAAI,UAAU,KAAA,GAAW,MAAM,IAAI,MAAM,kBAAkB,KAAK,2CAA2C;EAC3G,QAAQ,IAAI,MAAM,KAAK;CACzB;CACA,OAAO;AACT;;;;;;AAOA,MAAa,sBAA8B;CACzC,MAAM,UAAU,YAAY,KAAK,OAAO,GAAG,mBAAmB,CAAC;CAC/D,MAAM,SAAS,KAAK,SAAS,WAAW;CACxC,UAAU,MAAM;CAChB,IAAI,YAAY,GAAG;EACjB,MAAM,UAAU,mBAAmB,SAAS,SAAS,CAAC,YAAY,kBAAkB,MAAM,CAAC;EAC3F,aAAa,QAAQ,CAAC,GAAG,SAAS,OAAO,GAAG;GAAE,KAAK;GAAM;GAAO,OAAO;EAAS,CAAC;EACjF,KAAK,MAAM,QAAQ,oBACjB,aAAa,QAAQ;GAAC;GAAQ;GAAsB;EAAM,GAAG;GAAE,KAAK,KAAK,MAAM,YAAY,IAAI;GAAG;GAAO,OAAO;EAAS,CAAC;CAC9H,OACE,KAAK,MAAM,UAAU,iBAAiB,CAAC,CAAC,OAAO,GAC7C,aAAa,OAAO;EAAC;EAAQ;EAAsB;EAAQ;CAAkB,GAAG;EAAE,KAAK;EAAQ;EAAO,OAAO;CAAS,CAAC;CAE3H,KAAK,MAAM,CAAC,IAAI,SAAS,OAAO,aAAa,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;CACnF,OAAO;AACT;AAEA,IAAI,QAAgC;;;;;;AAOpC,MAAa,iBAAkC;CAC7C,IAAI,UAAU,MAAM,OAAO;CAC3B,MAAM,YAAY,YAAY;EAC5B,MAAM,UAAU,cAAc;EAC9B,IAAI;GACF,MAAM,MAAM,WAAW,oBAAoB,OAAO;GAClD,MAAM,EAAE,qBAAqB,MAAM,OAAO;GAC1C,MAAM,iBAAiB,eAAe,OAAO,CAAC,CAAC,MAAM,KAAK,EAAE,cAAc,MAAM,CAAC;GACjF,OAAO;EACT,UAAU;GACR,OAAO,SAAS;IAAE,WAAW;IAAM,OAAO;GAAK,CAAC;EAClD;CACF,EAAA,CAAG;CAEH,SAAS,YAAY;EACnB,IAAI,UAAU,UAAU,QAAQ;CAClC,CAAC;CACD,QAAQ;CACR,OAAO;AACT;;;;;;;;;AC3GA,MAAa,gBAAgB;;;;;;;;AAS7B,MAAa,eAAmH;CAC9H,OAAO;CACP,OAAO;CACP,KAAK;CACL,SAAS;AACX;;AAGA,MAAa,cAAsF;CACjG,SAAS,CAAC,SAAS,OAAO;CAC1B,SAAS;EAAC;EAAS;EAAS;EAAO;CAAS;AAC9C;;;;;;;;AASA,MAAa,oBAAsD;CACjE,OAAO;CACP,MAAM;CACN,OAAO;CACP,SAAS;CACT,MAAM;CACN,UAAU;CACV,MAAM;CACN,SAAS;CACT,OAAO;CACP,QAAQ;CACR,UAAU;CACV,SAAS;CACT,aAAa;AACf;;;;;;;;;;;;;;;;;AA0BA,MAAa,iBAAiB,QAAoB,WAAgD;CAChG,MAAM,UAAU,WAAW;CAC3B,IAAI,YAAY,OAAO,QAAQ,KAAA,KAAa,OAAO,YAAY,KAAA,IAAY,MAAM,IAAI,MAAM,8DAA8D;CACzJ,OAAO;EACL,YAAY;EACZ,MAAM;EACN,UAAU;GAAE,MAAM;GAAO,WAAW;EAAe;EACnD,MAAM;GACJ,SAAS,UAAU,YAAY;GAC/B,KAAK;GACL,KAAK;GACL,YAAY;IACV,eAAe;IACf,mBAAmB;IACnB,iBAAiB,KAAK,UAAU,iBAAiB;GACnD;GACA,QAAQ;IACN,QAAQ;KAAE,iBAAiB;KAAS,OAAO,EAAE,OAAO,OAAO,MAAM;KAAG,YAAY,EAAE,OAAO,OAAO,MAAM;IAAE;IACxG,OAAO;KAAE,OAAO,OAAO;KAAO,kBAAkB;KAAO,cAAc;KAAG,WAAW,EAAE,UAAU;MAAE,KAAK;MAAQ,QAAQ;KAAQ,EAAE;IAAE;IAClI,GAAI,UAAU;KAAE,KAAK,EAAE,OAAO,OAAO,IAAI;KAAG,SAAS;MAAE,OAAO,OAAO;MAAS,iBAAiB;KAAQ;IAAE,IAAI,CAAC;GAChH;EACF;CACF;AACF;;AAGA,MAAa,iBAAkE;CAC7E,SAAS,CAAC,mBAAmB;CAC7B,SAAS;EAAC;EAAqB;EAA4B;CAAmB;AAChF;;;;;;;;AASA,MAAa,cAAc,OAAO,SAAkB,QAAoB,WAAsC;CAC5G,MAAM,WAAW,MAAM,WAAW,QAAQ;CAC1C,MAAM,OAAO,KAAK,QAAQ,KAAK,SAAS,OAAO,MAAM;CACrD,cAAc,MAAM,GAAG,KAAK,UAAU,cAAc,QAAQ,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG;CACjF,IAAI;EACF,MAAM,QAAQ,UAAU;GAAC;GAAW;GAAgB,QAAQ;GAAY;GAAM;GAAM;GAAM;GAAuB;EAAO,GAAG,EAAE,SAAS,IAAQ,CAAC;EAC/I,KAAK,MAAM,YAAY,eAAe,SAAS,MAAM,QAAQ,QAAQ;GAAC;GAAM;GAAgB;GAAW;GAAU;GAAU;EAAgB,CAAC;CAC9I,SAAS,OAAO;EACd,MAAM,QAAQ,OAAO,UAClB,MAAM,QAAQ,QAAQ;GAAC;GAAM;GAAgB,GAAG;EAAI,CAAC,CAAC,CAAC,OAAO,QAAiB,OAAO,GAAG,CAAC,EAAA,CAAG,MAAM,IAAK;EAC3G,MAAM,OAAO,MAAM,MAAM;GAAC;GAAO;GAAQ;GAAM;EAAM,CAAC;EACtD,MAAM,MAAM,WAAW,YAAY,4BAA4B,MAAM,MAAM;GAAC;GAAQ;GAA4B;GAAa;EAAkB,CAAC,MAAM;EACtJ,MAAM,IAAI,MAAM,wBAAwB,OAAO,WAAW,OAAO,KAAK,EAAE,+BAA+B,OAAO,KAAK;CACrH;AACF;;AAGA,MAAa,WAAW,SAAsD,KAAK,MAAM,UAAU;;AAGnG,MAAa,iBAAiB,cAA8B,gBAAA,OAAwB,WAAW,EAAE;;AAGjG,MAAa,YAAsB;CAAE,OAAO;CAAI,MAAM;AAAK;;AAG3D,MAAa,YAAsB;CAAE,OAAO;CAAI,MAAM;AAAK;;AAG3D,MAAa,kBAAkB,SAA0C,KAAK,MAAM,SAAS;;AAG7F,MAAa,kBAAkB,SAAsD,KAAK,MAAM,SAAS,aAAa,KAAK,QAAQ,SAAS;;;;;;;;;ACpJ5I,MAAa,aAAa;;;;;AAY1B,MAAa,oBAAoB,SAAwC;CACvE,IAAI,MAAqB;CACzB,IAAI,UAAU;CACd,KAAK,MAAM,QAAQ,KAAK,MAAM,IAAI,GAAG;EACnC,MAAM,QAAQ,kEAAkE,KAAK,KAAK,KAAK,CAAC;EAChG,IAAI,UAAU,MAAM;EACpB,IAAI,MAAM,OAAO,YAAY,MAAM,OAAO,MAAM,EAAE;OAC7C,UAAU,OAAO,MAAM,EAAE;CAChC;CACA,OAAO,QAAQ,OAAO,OAAO;EAAE;EAAK;CAAQ;AAC9C;;;;ACkDA,MAAa,aAAa,SAAyB;CACjD,MAAM,QAAQ,mDAAmD,KAAK,IAAI;CAC1E,IAAI,UAAU,MAAM,MAAM,IAAI,MAAM,+BAA+B,KAAK,KAAK,CAAC,CAAC,MAAM,GAAG,GAAG,GAAG;CAC9F,OAAO,OAAO,MAAM,EAAE;AACxB;;;;ACjEA,MAAa,8BAAmC,IAAI,IAAI;CAAC;CAAU;CAAU;CAAU;CAAS;CAAW;CAAS;AAAM,CAAC;;AA+E3H,MAAM,SAAS,YAAY;CACzB,MAAM,EAAE,qBAAqB,MAAM,OAAO;CAC1C,OAAO,cAAc,iBAAiB;EACpC,YAAY,MAAoB;GAC9B,KAA0D,WAAW,UAAU;GAC/E,OAAO;EACT;CACF;AACF;;AAGA,MAAM,WAAW,UAAmD,WAAkC;CACpG,IAAI,UAAU;CACd,OAAO,GAAG,SAAS,UAA2B;EAC5C,WAAW,MAAM,SAAS;EAC1B,MAAM,QAAQ,QAAQ,MAAM,IAAI;EAChC,UAAU,MAAM,IAAI,KAAK;EACzB,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,UAAU,KAAK,KAAK;GAC1B,IAAI,CAAC,QAAQ,WAAW,GAAG,GAAG;GAC9B,IAAI;IACF,KAAK,KAAK,MAAM,OAAO,CAA4B;GACrD,QAAQ,CAER;EACF;CACF,CAAC;AACH;;AAGA,MAAM,iBAAiB,WAA0B;CAC/C,MAAM,QAAQ,KAAK,MAAM,OAAO,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,EAAE,CAAE;CAC1D,OAAO;EAAE,IAAI,OAAO,OAAO,MAAM,EAAE,CAAC,IAAI;EAAK,IAAI,OAAO,OAAO,MAAM,EAAE,CAAC,IAAI;CAAI;AAClF;AAEA,MAAM,SAAS,OAAO,WAAiC,SAA6C;CAClG,MAAM,SAAS,MAAM,UAAU,KAAK,CAAC,GAAG,IAAI,CAAC;CAC7C,IAAI,OAAO,aAAa,GAAG,MAAM,IAAI,MAAM,GAAG,KAAK,KAAK,GAAG,EAAE,UAAU,OAAO,SAAS,IAAI,OAAO,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,GAAG,GAAG;CAC/H,OAAO,OAAO;AAChB;;;;;;;AAQA,MAAa,cAAc,OAAO,SAAkB,UAAoC;CACtF,MAAM,UAAU,MAAM,aAAa;CACnC,MAAM,SAAS,MAAM,OAAO;CAC5B,MAAM,EAAE,qBAAqB,MAAM,OAAO;CAC1C,IAAI,QAAgB,CAAC;CACrB,MAAM,UAAkC,CAAC;CACzC,MAAM,4BAAY,IAAI,IAA0B;CAChD,MAAM,QAAQ,SAAqB;EACjC,MAAM,KAAK,IAAI;EACf,KAAK,MAAM,YAAY,WAAW,SAAS,IAAI;CACjD;CACA,MAAM,2BAAW,IAAI,IAAsB;CAC3C,MAAM,SAAiC,CAAC;CACxC,IAAI,UAAuC;CAC3C,IAAI,SAAsC;CAC1C,IAAI;EACF,KAAK,MAAM,QAAQ,QAAQ,OAAO;GAChC,MAAM,QAAQ,MAAM,IAAI,OAAO,KAAK,CAAC,CAClC,YAAY,aAAa,MAAM,CAAC,CAChC,mBAAmB,CAAC,CACpB,gBAAgB,MAAM,CAAC,CACvB,gBAAgB,EAAE,WAAW,KAAK,CAAC,CAAC,CACpC,YAAY;IAAC;IAAQ;IAAa;GAAO,CAAC,CAAC,CAC3C,gBAAgB,SAAS,SAAS,KAAK;IAAE,GAAI;IAA0B;GAAK,CAAC,CAAC,CAAC,CAAC,CAChF,MAAM;GACT,OAAO,KAAK,KAAK;GACjB,MAAM,WAAW,MAAM,IAAI,OAAO,OAAO,CAAC,CACvC,YAAY,aAAa,MAAM,CAAC,CAChC,mBAAmB,CAAC,CACpB,gBAAgB,MAAM,CAAC,CACvB,MAAM;GACT,OAAO,KAAK,QAAQ;GACpB,SAAS,IAAI,MAAM;IAAE;IAAO;GAAS,CAAC;EACxC;CACF,SAAS,OAAO;EACd,KAAK,MAAM,aAAa,OAAO,QAAQ,GAAG,MAAM,UAAU,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,YAAY,KAAA,CAAS;EACpG,MAAM;CACR;CACA,MAAM,aAAa,SAA2B;EAC5C,MAAM,QAAQ,SAAS,IAAI,IAAI;EAC/B,IAAI,UAAU,KAAA,GAAW,MAAM,IAAI,MAAM,2BAA2B,MAAM;EAC1E,OAAO;CACT;CACA,MAAM,YAAY,QAAQ,MAAM;;;;;CAKhC,MAAM,YAAY,OAChB,MACA,QACA,SACkB;EAClB,KAAK,MAAM,CAAC,MAAM,EAAE,YAAY,UAAU;GACxC,MAAM,OAAO,SAAS,OAAO,CAAC,IAAI,KAAK,KAAK,QAAQ,QAAQ,IAAI,SAAS,IAAI,CAAC,CAAC,KAAK,EAAE,KAAK,WAAW;IAAE;IAAK;GAAI,EAAE;GAEnH,MAAM,OAAO,SAAS,QAAQ,KAAK,WAAW,IAAI,OAAO,KAAK,UAAU;IAAE,GAAG;IAAQ,OAAO,QAAQ,KAAK,OAAO,IAAI;IAAG;GAAK,CAAC;GAC7H,IAAI,SAAS,MAAM,MAAM,OAAO,OAAO;IAAC;IAAM;IAAM,QAAQ,KAAK;GAAM,CAAC;QAEnE,MAAM,OAAO,OAAO;IAAC;IAAM;IAAM,gBAAgB,KAAK,WAAW,KAAK,2BAA2B,KAAK,qBAAqB,KAAK;GAAM,CAAC;EAC9I;CACF;CACA,MAAM,MAAe;EACnB;EACA;EACA,IAAI,QAAQ;GACV,OAAO;EACT;EACA;EACA,MAAM;GACJ,OAAO;IAAC,GAAG;IAAQ,GAAI,YAAY,OAAO,CAAC,IAAI,CAAC,OAAO;IAAI,GAAI,WAAW,OAAO,CAAC,IAAI,CAAC,MAAM;GAAE,CAAC,CAAC,KAAK,cAAc,UAAU,QAAQ,CAAC,CAAC,QAAQ,OAAO,EAAE,CAAC;EAC5J;EACA,OAAO;GACL,MAAM,MAAM;GACZ,QAAQ,CAAC;GACT,OAAO;EACT;EACA,OAAO,UAAU;GACf,UAAU,IAAI,QAAQ;GACtB,aAAa,UAAU,OAAO,QAAQ;EACxC;EACA,MAAM,MAAM,YAAY,SAAS;GAC/B,IAAI,YAAY,MAAM,MAAM,QAAQ,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,YAAY,KAAA,CAAS;GAC9E,MAAM,OAAO,MAAM,QAAQ,YAAY;GACvC,UAAU,MAAM,IAAI,iBAAiB,KAAK,CAAC,CACxC,gBAAgB,QAAQ,OAAO,CAAC,CAChC,gBAAgB;IAAE,MAAM,KAAK,UAAU,IAAI;IAAG,YAAY,WAAW,KAAK,GAAG;IAAG,SAAS,SAAS,YAAY,OAAO,MAAM;GAAI,CAAC,CAAC,CACjI,YAAY,CAAC,QAAQ,WAAW,CAAC,CAAC,CAClC,gBAAgB,SAAS,SAAS,KAAK,IAAuB,CAAC,CAAC,CAAC,CACjE,MAAM;EACX;EACA,MAAM,MAAM,OAAO;GACjB,MAAM,IAAI,UAAU;GACpB,IAAI,cAAkD,CAAC;GACvD,MAAM,QAAQ,IAAI,SAAe,SAAS,WAAW;IACnD,SAAS,IAAI,QAAS,KAAK,QAAQ,IAAI,uBAAO,IAAI,MAAM,sBAAsB,KAAK,CAAC;GACtF,CAAC;GACD,SAAS,MAAM,IAAI,iBAAiB,OAAO,CAAC,CACzC,mBAAmB,CAAC,CACpB,gBAAgB,MAAM,CAAC,CACvB,YAAY,CAAC,uBAAuB,GAAG,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,CAC1D,gBACC,SAAS,WAAW;IAClB,MAAM,KAAK,OAAO,OAAO,KAAK;IAC9B,IAAI,OAAO,SAAS,MAAM,MAAM,EAAE;IAClC,IAAI,OAAO,QAAQ,MAAM,OAAO,OAAO,OAAO,MAAM,CAAC;IACrD,IAAI,CAAC,YAAY,IAAI,EAAE,KAAK,OAAO,SAAS;KAC1C,QAAQ,OAAO,QAAQ,OAAO,KAAK;KACnC;IACF;IACA,MAAM,OAAO,OAAO,OAAO,WAAW,CAAC;IACvC,KAAK;KAAE,GAAI;KAA4B,IAAI,UAAU;KAAM,GAAG,OAAO;IAAI,CAAC;GAC5E,CAAC,CACH,CAAC,CACA,MAAM;GACT,MAAM,QAAQ,KAAK,CACjB,OACA,IAAI,SAAS,GAAG,WAAW,iBAAiB,uBAAO,IAAI,MAAM,uCAAuC,CAAC,GAAG,GAAM,CAAC,CACjH,CAAC;EACH;EACA,MAAM,YAAY;GAChB,IAAI,WAAW,MAAM,MAAM,OAAO,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,YAAY,KAAA,CAAS;GAC5E,SAAS;EACX;EACA,MAAM,MAAM,MAAM;GAEhB,MAAM,OAAO,SAAS,OAAO,OAAO,KAAK,UAAU;IAAE,GAAG;IAAM,OAAO,QAAQ,KAAK,OAAO,IAAI;GAAE,CAAC;GAChG,KAAK,MAAM,EAAE,WAAW,SAAS,OAAO,GACtC,IAAI,SAAS,MAAM,MAAM,OAAO,OAAO;IAAC;IAAM;IAAM;GAAiB,CAAC;QACjE,MAAM,OAAO,OAAO;IAAC;IAAM;IAAM,gBAAgB,KAAK;GAAoB,CAAC;EAEpF;EACA,MAAM,IAAI,MAAM;GACd,MAAM,UAAU,OAAO,SAAS,OAAO,OAAO,EAAE,SAAS,KAAK,QAAQ,GAAG,IAAI;EAC/E;EACA,MAAM,IAAI,MAAM;GACd,MAAM,UAAU,OAAO,CAAC,GAAG,IAAI;EACjC;EACA,MAAM,iBAAiB,IAAI,MAAM;GAC/B,MAAM,MAA8B,CAAC;GACrC,KAAK,MAAM,CAAC,MAAM,EAAE,YAAY,UAAU;IACxC,MAAM,OAAO;KAAC;KAAW;KAAM;KAAK;KAAM;KAAM;KAAM;KAAa;KAAM;KAAM;KAAO;KAAc;KAAI;KAAW,OAAO,IAAI;KAAG;KAAW;IAAU;IAEtJ,MAAM,SAAS,MAAM,MAAM,KAAK,IAAI;IACpC,IAAI,OAAO,aAAa,KAAK,OAAO,aAAa,GAAG,MAAM,IAAI,MAAM,GAAG,KAAK,KAAK,GAAG,EAAE,UAAU,OAAO,SAAS,IAAI,OAAO,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,GAAG,GAAG;IACxJ,IAAI,QAAQ,UAAU,OAAO,MAAM;GACrC;GACA,OAAO;EACT;EACA,MAAM,IAAI,MAAM,MAAM;GACpB,MAAM,SAAS,MAAM,OAAO,UAAU,IAAI,CAAC,CAAC,OAAO;IAAC;IAAQ;IAAa;IAAO,KAAK,UAAU,IAAI;GAAC,CAAC;GACrG,MAAM,SAAS,KAAK,MAAM,OAAO,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,EAAE,CAAE;GAC3D,IAAI,CAAC,OAAO,IAAI,MAAM,IAAI,MAAM,WAAW,KAAK,UAAU,IAAI,EAAE,MAAM,KAAK,WAAW,OAAO,KAAK;GAClG,KAAK;IAAE,IAAI;IAAO,GAAG,OAAO;IAAI,IAAI,OAAO;IAAI;IAAM;GAAK,CAAC;GAC3D,OAAO;IAAE,IAAI,OAAO;IAAI,IAAI,OAAO;GAAG;EACxC;EACA,MAAM,OAAO,MAAM,KAAK,MAAM;GAC5B,MAAM,SAAS,MAAM,OAAO,UAAU,IAAI,CAAC,CAAC,UAAU;IAAC;IAAW;IAAM,OAAO,GAAG;IAAG;IAAM;IAAuB,GAAG;GAAI,CAAC;GAC1H,MAAM,QAAQ,cAAc,MAAM;GAClC,KAAK;IAAE,IAAI;IAAO,GAAG,MAAM;IAAI,IAAI,MAAM;IAAI;IAAM,MAAM;KAAE,IAAI;KAAU;KAAK;IAAK;GAAE,CAAC;GACtF,OAAO;EACT;EACA,MAAM,OAAO,MAAM,KAAK,OAAO;GAC7B,MAAM,IAAI,MAAM,IAAI,IAAI;GACxB,MAAM,OAAO,MAAM,OAAO,UAAU,IAAI,CAAC,CAAC,UAAU;IAAC;IAAW;IAAM,OAAO,GAAG;IAAG;IAAM;IAAiB;IAAM;IAAM;GAAK,CAAC;GAC5H,KAAK;IAAE,IAAI;IAAU;IAAG;IAAM;IAAK;IAAO,MAAM,KAAK,MAAM,GAAG,GAAI;GAAE,CAAC;GACrE,OAAO;EACT;EACA,MAAM,MAAM;GACV,QAAQ,MAAM,IAAI,IAAI,WAAW,EAAE,IAAI,MAAM,CAAC,EAAA,CAAG;EACnD;EACA,MAAM,MAAM,MAAM,WAAW,KAAK,WAAW;GAC3C,MAAM,OACJ,MAAM,QAAQ,KAAK,MAAM;IACvB;IACA;IACA;IACA;IACA;IACA;IACA,+BAA+B;IAC/B;IACA,0BAA0B;IAC1B;IACA,IAAI,UAAU;GAChB,CAAC,EAAA,CAEA,MAAM,KAAK,CAAC,CACZ,QAAQ,OAAO,OAAO,EAAE;GAC3B,IAAI,IAAI,WAAW,GAAG,MAAM,IAAI,MAAM,GAAG,UAAU,GAAG,IAAI,GAAG,UAAU,MAAM,KAAK,OAAO,IAAI,OAAO,2BAA2B;GAE/H,MAAM,MADO,KAAK,MAAM,MAAM,QAAQ,KAAK,MAAM;IAAC;IAAU;IAAW,IAAI;GAAG,CAAC,CAChE,CAAC,CAAC,MAAM;GACvB,IAAI,QAAQ,KAAA,KAAa,EAAE,MAAM,IAAI,MAAM,IAAI,MAAM,2BAA2B,UAAU,GAAG,IAAI,GAAG,WAAW;GAC/G,OAAO;EACT;EACA,MAAM,QAAQ,MAAM,KAAK;GACvB,MAAM,QAAQ,MAAM,QAAQ,KAAK,MAAM,CAAC,YAAY,SAAS,IAAI,QAAQ,CAAC,EAAA,CAAG,KAAK;GAClF,MAAM,QAAQ,kBAAkB,KAAK,IAAI,CAAC,GAAG;GAC7C,IAAI,UAAU,KAAA,GAAW,MAAM,IAAI,MAAM,4BAA4B,MAAM;GAC3E,OAAO,OAAO,KAAK;EACrB;EACA,MAAM,SAAS,MAAM,KAAK;GACxB,MAAM,QAAQ,MAAM,QAAQ,KAAK,MAAM,CAAC,OAAO,SAAS,IAAI,QAAQ,CAAC,EAAA,CAAG,KAAK;GAC7E,MAAM,OAAO,cAAc,KAAK,IAAI,CAAC,GAAG;GAExC,IAAI,SAAS,KAAA,KAAa,CAAC,wCAAwC,KAAK,IAAI,GAC1E,MAAM,IAAI,MAAM,iBAAiB,IAAI,MAAM,KAAK,sCAAsC,MAAM;GAC9F,OAAO,6BAA6B;EACtC;EACA,MAAM,QAAQ;GACZ,MAAM,IAAI,UAAU;GACpB,IAAI,YAAY,MAAM,MAAM,QAAQ,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,YAAY,KAAA,CAAS;GAC9E,UAAU;GACV,KAAK,MAAM,aAAa,OAAO,QAAQ,GAAG,MAAM,UAAU,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,YAAY,KAAA,CAAS;EACtG;CACF;CACA,OAAO;AACT;;AAGA,MAAa,WAAW,OAAO,UAAmC;CAChE,MAAM,QAAQ,kDAAkD,KAAK,KAAK;CAC1E,IAAI,UAAU,MAAM,MAAM,IAAI,MAAM,GAAG,MAAM,2BAA2B;CACxE,MAAM,GAAG,YAAY,KAAK,UAAU;CACpC,MAAM,QAAQ,eAAe,WAAY,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,GAAG,OAAO,SAAS,GAAG,OAAQ,MAAM,GAAG,EAAE;CAEpG,IAAI,EAAC,MADiB,QAAQ,UAAU;EAAC;EAAS;EAAW;EAAY;EAAyB,GAAG,WAAW,UAAU;CAAQ,CAAC,CAAC,CAAC,YAAY,EAAE,EAAA,CACtI,SAAS,MAAO,GAAG,MAAM,QAAQ,UAAU,CAAC,QAAQ,GAAG,WAAW,UAAU,QAAQ,GAAG,EAAE,SAAS,IAAQ,CAAC;CACxH,MAAM,QAAQ,UAAU;EAAC;EAAO,GAAG,WAAW,UAAU;EAAU;CAAK,CAAC;CACxE,OAAO;AACT;;;;;;;;;;ACxWA,MAAa,WAAW,SAAyC,KAAK,SAAS;;AAK/E,MAAa,cAAc;AAE3B,MAAM,SAAS,QAAkB,SAAyB;CACxD,MAAM,UAAU,QAAQ,QAAQ,GAAG;CACnC,IAAI,CAAC,OAAO,UAAU,OAAO,KAAK,UAAU,GAAG,MAAM,IAAI,WAAW,GAAG,KAAK,MAAM,QAAQ,kCAAkC;CAC5H,OAAO;AACT;;AAGA,MAAa,eAAe,SAC1B,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,EAAE;;AAGzE,MAAM,cAA4E;CAChF,SAAS,EAAE,mBAAmB,UAAU;CACxC,SAAS,EAAE,2BAA2B,UAAU;AAClD;;;;;;;AAQA,MAAa,mBAAmB,MAA8B,eAAgD;CAC5G,YAAY;CACZ,MAAM;CACN,UAAU;EAAE,MAAM;EAAW,GAAI,KAAK,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,YAAY,KAAK,KAAK,MAAM,EAAE;CAAG;AAClH;;;;;;;;AASA,MAAa,WAAW,MAAqB,OAAqB,aAA+C;CAC/G,GAAI,SAAS,OAAO,EAAE,MAAM,YAAY,KAAA,IAAY,EAAE,MAAM,KAAK,IAAI;EAAE,MAAM;EAAM;CAAQ,EAAE,IAAI,EAAE,SAAS;EAAE;EAAM,MAAM;CAAY,EAAE;CACxI,qBAAqB;CACrB,eAAe,MAAM,MAAM,QAAQ,iBAAiB,QAAQ,kBAAkB;CAC9E,gBAAgB,MAAM,MAAM,SAAS,kBAAkB,QAAQ,kBAAkB;CACjF,kBAAkB,MAAM;CACxB,kBAAkB;AACpB;;AAGA,MAAM,UAAU;CAAE,MAAM;CAAY,WAAW,EAAE,UAAU,EAAE,WAAW,gBAAgB,EAAE;AAAE;;AAG5F,MAAM,UAAU,SAAmD,CACjE;CAAE,MAAM;CAAU,OAAO,KAAK,UAAU;EAAE,MAAA;EAAc,MAAM;EAAM,YAAY;EAAa,aAAa,KAAK;CAAY,CAAC;AAAE,GAC9H,OACF;;AAGA,MAAM,YAAY;;;;;;;;;;;AAYlB,MAAM,mBAAmB,MAAgB,YAA6C;CACpF,MAAM,MAAM;EAAE,MAAM;EAAe,OAAO,OAAO,OAAO;CAAE;CAC1D,IAAI,QAAQ,IAAI,MAAM,WACpB,OAAO;EACL,SAAS,CAAC,QAAQ,SAAS;EAC3B,KAAK,CAAC,GAAG,OAAO,IAAI,GAAG,GAAG;EAC1B,OAAO,CAAC,EAAE,eAAe,KAAK,GAAG,EAAE,eAAe,YAAY,CAAC;EAC/D,GAAI,KAAK,IAAI,YAAY,OAAO,CAAC,IAAI,EAAE,cAAc,QAAQ,YAAY,KAAK,IAAI,OAAO,EAAE;EAC3F,gBAAgB,QAAQ,cAAc,KAAK,IAAI,SAAS;EACxD,eAAe,QAAQ,aAAa,KAAK,IAAI,QAAQ;CACvD;CACF,MAAM,OAAO,KAAK,QAAQ,SAAS;CACnC,OAAO;EACL,SAAS;GAAC;GAAkB;GAAU,OAAO,IAAI;GAAG;GAAU;GAAe;GAAW;GAAM,GAAI,OAAO,CAAC,eAAe,SAAS,IAAI,CAAC;EAAE;EACzI,KAAK,CAAC,SAAS,GAAG;EAClB,OAAO,CAAC,EAAE,eAAe,KAAK,CAAC;EAC/B,GAAI,KAAK,IAAI,YAAY,OAAO,CAAC,IAAI,EAAE,cAAc,QAAQ,MAAM,KAAK,IAAI,OAAO,EAAE;EACrF,gBAAgB,QAAQ,MAAM,KAAK,IAAI,SAAS;EAChD,eAAe,OAAO,QAAQ,MAAM,KAAK,IAAI,UAAU,UAAU,IAAI,QAAQ,MAAM,KAAK,IAAI,QAAQ;CACtG;AACF;;AAGA,MAAa,gBAAgB,WAAmB,UAA6B,CAC3E;CACE,YAAY;CACZ,MAAM;CACN,UAAU;EAAE,MAAM;EAAO;EAAW,QAAQ,EAAE,KAAK,MAAM;CAAE;CAC3D,MAAM;EACJ,UAAU;EACV,UAAU,EAAE,aAAa,EAAE,KAAK,MAAM,EAAE;EACxC,UAAU;GACR,UAAU,EAAE,QAAQ,EAAE,KAAK,MAAM,EAAE;GACnC,MAAM;IACJ,+BAA+B;IAC/B,YAAY,CACV;KACE,MAAM;KACN;KACA,iBAAiB;KACjB,SAAS;MAAC;MAAgB;MAAU;MAAI;MAAgB;KAAI;KAC5D,OAAO,CAAC,EAAE,eAAe,KAAK,CAAC;KAC/B,gBAAgB;MAAE,WAAW,EAAE,MAAM,KAAK;MAAG,eAAe;MAAG,kBAAkB;MAAG,kBAAkB;MAAG,gBAAgB;KAAE;IAC7H,CACF;GACF;EACF;CACF;AACF,GACA;CACE,YAAY;CACZ,MAAM;CACN,UAAU;EAAE,MAAM;EAAO;CAAU;CACnC,MAAM;EAAE,UAAU,EAAE,KAAK,MAAM;EAAG,OAAO,CAAC;GAAE,MAAM;GAAM,YAAY;EAAK,CAAC;CAAE;AAC9E,CACF;;;;;;;;AASA,MAAM,aAAa,SAAiB,UAA2C;CAC7E,IAAI,YAAY,GAAG,OAAO,CAAC;CAC3B,IAAI,WAAW,OAAO,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,QAAQ,EAAE,EAAE,EAAE;CACvF,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS;EAAC;EAAW;EAAM,SAAS;CAAS,EAAE,EAAE,EAAE,EAAE;AAChG;;;;;AAMA,MAAa,iBAAiB,MAAgB,WAAmB,OAAe,UAAU,MAAiB;CACzG,MAAM,UAAU,MAAM,KAAK,IAAI,SAAS,mBAAmB;CAC3D,MAAM,QAAQ,MAAM,KAAK,IAAI,OAAO,kBAAkB;CACtD,OAAO,CACL;EACE,YAAY;EACZ,MAAM;EACN,UAAU;GAAE,MAAA;GAAc;EAAU;EACpC,MAAM;GAAE,UAAU,EAAE,KAAA,MAAY;GAAG,OAAO,CAAC;IAAE,MAAM,QAAQ,IAAI,MAAM,SAAS,SAAS;IAAQ,MAAM;IAAI,YAAY;GAAK,CAAC;EAAE;CAC/H,GACA;EACE,YAAY;EACZ,MAAM;EACN,UAAU;GAAE,MAAA;GAAc;GAAW,QAAQ,EAAE,KAAA,MAAY;EAAE;EAC7D,MAAM;GACJ,UAAU,KAAK,IAAI;GACnB,UAAU,EAAE,aAAa,EAAE,KAAA,MAAY,EAAE;GACzC,UAAU;IAAE,MAAM;IAAiB,eAAe;KAAE,UAAU;KAAO,gBAAgB;IAAM;GAAE;GAC7F,iBAAiB;GACjB,UAAU;IACR,UAAU,EAAE,QAAQ;KAAE,KAAA;MAAc,YAAY;KAAK,uBAAuB,OAAO,OAAO;IAAE,EAAE;IAC9F,MAAM;KACJ,+BAA+B;KAC/B,oBAAoB;KACpB,2BAA2B,CACzB;MACE,SAAS;MACT,aAAa;MACb,mBAAmB;MACnB,eAAe,EAAE,aAAa,EAAE,KAAA,MAAY,EAAE;KAChD,CACF;KACA,YAAY,CACV;MACE,MAAM;MACN;MACA,iBAAiB;MACjB,GAAG,gBAAgB,MAAM,OAAO;MAChC,GAAG,UAAU,SAAS,KAAK;KAC7B,CACF;IACF;GACF;EACF;CACF,CACF;AACF;;;;;;AAOA,MAAa,iBAAiB,MAAgB,WAAmB,OAAe,WAA8B,CAC5G;CACE,YAAY;CACZ,MAAM;CACN,UAAU;EAAE,MAAM;EAAQ;EAAW,QAAQ,EAAE,KAAK,OAAO;CAAE;CAC7D,MAAM;EACJ,UAAU,EAAE,aAAa,EAAE,KAAK,OAAO,EAAE;EACzC,UAAU;GACR,UAAU,EAAE,QAAQ;IAAE,KAAK;KAAS,YAAY;GAAI,EAAE;GACtD,MAAM;IACJ,+BAA+B;IAC/B,aAAa,CAAC;KAAE,KAAK;KAAyC,UAAU;KAAU,QAAQ;IAAa,CAAC;IACxG,YAAY,CACV;KACE,MAAM;KACN;KACA,iBAAiB;KACjB,SAAS,CAAC,QAAQ,QAAQ,IAAI,MAAM,SAAS,oBAAoB,YAAY;KAC7E,KAAK;MACH;OAAE,MAAM;OAAU,OAAO;MAAO;MAChC;OAAE,MAAM;OAAY,OAAO,OAAO,QAAQ,KAAK,SAAS,OAAO,IAAI,CAAC;MAAE;MACtE;OAAE,MAAM;OAAc,OAAO,OAAO,QAAQ,KAAK,SAAS,SAAS,IAAI,CAAC;MAAE;MAC1E;OAAE,MAAM;OAAY,WAAW,EAAE,UAAU,EAAE,WAAW,gBAAgB,EAAE;MAAE;MAC5E;OAAE,MAAM;OAAa,WAAW,EAAE,UAAU,EAAE,WAAW,gBAAgB,EAAE;MAAE;KAC/E;IACF,CACF;GACF;EACF;CACF;AACF,CACF;;;;;;;;;ACvOA,MAAMA,OAAK,WAA6B,QAAQ,QAAQ,GAAG;;AAE3D,MAAM,MAAM,UAA0B,KAAK,MAAM,QAAQ,GAAG,IAAI;AAChE,MAAM,QAAQ,IAAY,OAAqB,OAAO,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC;;AAcpE,MAAa,aAA0B;CAAE,IAAI,QAAQ,CAAC;CAAG,IAAI,QAAQ,CAAC;CAAG,MAAM;AAAsC;;;;;;AAOrH,MAAa,eAAe,OAAqB,UAAyB;CACxE,MAAM,OAAO,QAAQA,IAAE,MAAM,OAAO,IAAI;CACxC,OAAO,MAAM,MAAM,YAAY,KAAKA,IAAE,MAAM,MAAM,IAAI,MAAM,MAAM,YAAYA,IAAE,MAAM,MAAM,IAAI,IAAI;AACtG;;;;;;;AAQA,MAAa,aACX,QACA,MACA,SACA,mBACS;CACT,MAAM,QAAQ,SAAS,QAAQ,MAAM,OAAO;CAC5C,IAAI,YAAY,WAAW,OAAO,KAAKA,IAAE,MAAM,GAAG,GAAGA,IAAE,MAAM,GAAG,CAAC;CACjE,MAAM,OAAO,mBAAmB,KAAA,IAAYA,IAAE,KAAK,OAAO,IAAI,KAAK,IAAIA,IAAE,cAAc,GAAGA,IAAE,KAAK,OAAO,CAAC;CACzG,MAAM,OAAO,SAAS,QAAQ;EAAE,GAAG;EAAM,SAAS,QAAQ,IAAI;CAAE,GAAG,SAAS;CAC5E,OAAO,KAAKA,IAAE,MAAM,GAAG,GAAGA,IAAE,KAAK,GAAG,CAAC;AACvC;;AAGA,MAAa,eAAe,OAAa,OAAqB,gBAAmC;CAC/F,MAAM,UAAU,UAAU,YAAY,OAAO,KAAK,CAAC;CACnD,MAAM,OAAO,UAAU,KAAK;CAC5B,MAAM,SAAS,UAAU,WAAW;CACpC,OAAO,KAAK,KAAK,KAAK,QAAQ,KAAK,OAAO,IAAI,KAAK,KAAK,QAAQ,KAAK,OAAO,EAAE;AAChF;;;;;AAMA,MAAa,eAAe,OAAa,UAAwB,YAA4B;CAC3F,MAAM,UAAU,UAAU,YAAY,UAAU,KAAK,CAAC;CACtD,MAAM,OAAO,UAAU,KAAK;CAC5B,OAAO,KAAK,KAAK,KAAK,QAAQ,KAAKA,IAAE,OAAO,GAAG,KAAK,KAAK,QAAQ,KAAKA,IAAE,OAAO,CAAC;AAClF;;;;;AAMA,MAAa,oBAAoB,WAAyB,gBAAmC;CAC3F,MAAM,UAAU,UAAU,YAAY,WAAW,IAAI,CAAC;CACtD,MAAM,SAAS,UAAU,WAAW;CACpC,OAAO,KAAK,QAAQ,KAAK,OAAO,IAAI,QAAQ,KAAK,OAAO,EAAE;AAC5D;;AAGA,MAAa,SAAS,aAA0B,UAA2B;CACzE,MAAM,QAAQ,YAAY,OAAO,MAAM,CAAC,UAAU,SAAS,KAAK;CAChE,OAAO,QAAQ,UAAU,KAAA,IAAY,IAAIA,IAAE,MAAM,EAAE,CAAC;AACtD;;;;;;AAOA,MAAa,eAAe,aAA0B,YACpD,QAAQ,GAAGA,IAAE,OAAO,IAAIA,IAAE,MAAM,aAAa,gBAAgB,CAAC,CAAC,CAAC;;AAkBlE,MAAa,kBAAkB,aAA0B,SAAmB,gBAA6C;CACvH,MAAM,SAAS,YAAY,aAAa,OAAO;CAC/C,MAAM,SAAS,UAAU,WAAW;CACpC,OAAO;EAAE,OAAOA,IAAE,MAAM,KAAK,OAAO;EAAI,KAAK,KAAK,OAAO,IAAI,OAAO,EAAE;EAAG;CAAO;AAClF;;;;;;;AAQA,MAAa,gBACX,aACA,SACA,MACA,MACA,gBACmD;CACnD,MAAM,SAAS,YAAY,aAAa,OAAO;CAC/C,MAAM,SAAS,GAAGA,IAAE,IAAI,IAAIA,IAAE,IAAI,CAAC;CACnC,MAAM,SAAS,UAAU,WAAW;CACpC,OAAO;EAAE,OAAOA,IAAE,MAAM,KAAK,SAAS,OAAO;EAAI,KAAK,KAAK,OAAO,IAAI,OAAO,EAAE;EAAG;EAAQ,QAAQ,QAAQ,MAAM;CAAE;AACpH;;;;;;;;;;;;AC5HA,MAAa,MAAY;CACvB,IAAI;CACJ,MAAM;CACN,QAAQ;CACR,MAAM;EACJ,KAAK,EACH,OAAO,CACL;GAAE,MAAM;GAAiB,MAAM;GAAG,IAAI;EAAM,GAC5C;GAAE,MAAM;GAAkB,MAAM;GAAG,IAAI;EAAU,CACnD,EACF;EACA,IAAI;EACJ,KAAK,EAAE,OAAO,CAAC;GAAE,MAAM;GAAqB,MAAM;GAAG,IAAI;EAAU,CAAC,EAAE;CACxE;AACF;;AAGA,MAAa,MAAY;CACvB,IAAI;CACJ,MAAM;CACN,QAAQ;CACR,MAAM;EACJ,KAAK,EACH,OAAO,CACL;GAAE,MAAM;GAA8B,MAAM;GAAG,IAAI;EAAM,GACzD;GAAE,MAAM;GAAqB,MAAM;GAAG,IAAI;EAAU,CACtD,EACF;EACA,IAAI;EACJ,KAAK,EACH,OAAO,CACL;GAAE,MAAM;GAA6B,MAAM;GAAG,IAAI;EAAM,GACxD;GAAE,MAAM;GAAiB,MAAM;GAAG,IAAI;EAAM,CAC9C,EACF;CACF;AACF;;AAGA,MAAa,MAAY;CACvB,IAAI;CACJ,MAAM;CACN,QAAQ;CACR,MAAM;EACJ,KAAK,EACH,OAAO,CACL;GAAE,MAAM;GAAiB,MAAM;GAAG,IAAI;EAAM,GAC5C;GAAE,MAAM;GAAsB,MAAM;GAAG,IAAI;EAAU,CACvD,EACF;EACA,IAAI;EACJ,KAAK,EAAE,OAAO,CAAC;GAAE,MAAM;GAAqB,MAAM;GAAG,IAAI;EAAM,CAAC,EAAE;CACpE;AACF;;AAGA,MAAa,MAAY;CACvB,IAAI;CACJ,MAAM;CACN,QAAQ;CACR,MAAM;EACJ,KAAK,EAAE,OAAO,CAAC;GAAE,MAAM;GAAsB,MAAM;GAAG,IAAI;EAAU,CAAC,EAAE;EACvE,IAAI;EACJ,KAAK,EAAE,OAAO,CAAC;GAAE,MAAM;GAAsB,MAAM;GAAG,IAAI;EAAU,CAAC,EAAE;CACzE;CACA,WAAW,CACT;EACE,KAAK,EAAE,OAAO,CAAC;GAAE,MAAM;GAAsB,MAAM;GAAG,IAAI;EAAU,CAAC,EAAE;EACvE,IAAI;EACJ,KAAK,EACH,OAAO,CACL;GAAE,MAAM;GAAqB,MAAM;GAAG,IAAI;EAAM,GAChD;GAAE,MAAM;GAAiB,MAAM;GAAI,IAAI;EAAM,CAC/C,EACF;CACF,CACF;AACF;;AAGA,MAAa,cAA+B;CAAC;CAAK;CAAK;CAAK;AAAG;AAE/D,MAAMC,aAAW,WAA6B,QAAQ,QAAQ,GAAG;AACjE,MAAM,KAAK,WAA6B;CAAE;CAAO,MAAM;AAAI;;;;;AAM3D,MAAa,cAAc,MAAgB,iBAAwC;CACjF,UAAU;EACR,qBAAqB,KAAK,IAAI;EAC9B,yBAAyB,KAAK,IAAI;EAClC,oBAAoB,KAAK,IAAI,UAAU;EACvC,6BAA6B,YAAY;CAC3C;CACA,QAAQ;EACN,WAAW,EAAEA,UAAQ,KAAK,IAAI,UAAU,MAAM,IAAI,KAAK,IAAI,UAAU,SAAS;EAC9E,UAAU,EAAEA,UAAQ,KAAK,IAAI,SAAS,MAAM,IAAI,KAAK,IAAI,SAAS,SAAS;CAC7E;AACF;;AAMA,MAAa,YAAY,aAAiC,MAAgB,gBACxE,KAAK,aAAa,WAAW,MAAM,WAAW,GAAG,WAAW;;AAG9D,MAAa,cAAc,aACzB,OAAO,YAAY,SAAS,KAAK,YAAY,CAAC,QAAQ,MAAM,QAAQ,OAAO,CAAC,CAAC;;AAG/E,MAAa,gBAAgB,SAAuC;CAClE,IAAI,KAAK,gBAAgB,MAAM,MAAM,IAAI,MAAM,GAAG,KAAK,KAAK,8EAA8E;CAC1I,MAAM,QAAQ,KAAK,YAAY;CAC/B,MAAM,SACJ,UAAU,OACN,KAAA,IACA,YAAY,SAAS;EAAE,MAAM;EAAS,MAAM;EAAM,QAAQ;EAA2B,OAAO,MAAM;EAAO,iBAAiB,MAAM;CAAO,CAAC;CAC9I,OAAO,QAAQ,OAAO,KAAK,aAAa,EAAE,OAAO,OAAO,CAAC,CAAC,CAAC,SAAS;AACtE;;;;ACnHA,MAAa,cAAwB;CAAE,OAAO;CAAI,MAAM;AAAK;AAkB7D,MAAM,SAAS,OAAe,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;AAC9E,MAAM,QAAQ,SAAyB,KAAK,YAAY,CAAC,CAAC,QAAQ,eAAe,GAAG,CAAC,CAAC,QAAQ,UAAU,EAAE;;AAE1G,MAAM,YAAY,SAA4B,KAAK,gBAAgB,QAAQ,KAAK,YAAY,UAAU;;AAWtG,MAAM,cAAc,SAA4B,KAAK,SAAS,KAAA,IAAY,GAAG,KAAK,KAAK,GAAG,KAAK,YAAY,GAAG,KAAK,KAAK,GAAG,KAAK,QAAQ,GAAG,KAAK,KAAK;;;;;AAMrJ,IAAM,UAAN,MAAc;CACZ,uBAAgB,IAAI,IAAqB;CACzC,yBAAkB,IAAI,IAAkB;CACxC,2BAAoB,IAAI,IAA+B;CACvD,wBAAiB,IAAI,IAA+B;CACpD,0BAAmB,IAAI,IAA0D;;CAEjF,0BAAmB,IAAI,IAAoB;CAC3C,OAAO;CACP,OAAO,SAAqB;EAC1B,IAAI,OAAO,KAAK,MAAM,YAAY,KAAK,IAAI,KAAK,QAAQ,KAAK,OAAO,OAAO,KAAK,OAAO,KAAK;EAC5F,IAAI,KAAK,OAAO,OAAO;GACrB,MAAM,MAAM;GACZ,IAAI,IAAI,SAAS,WAAW,KAAK,KAAK,OAAO,IAAI,IAAI;QAChD,KAAK,KAAK,IAAI,IAAI,MAAM,GAAG;EAClC,OAAO,IAAI,KAAK,OAAO,SAAS;GAC9B,IAAI,KAAK,YAAY,WAAW,KAAK,OAAO,OAAO,OAAO,KAAK,OAAO,CAAC;QAClE,KAAK,OAAO,IAAI,OAAO,KAAK,OAAO,GAAG,IAAI;EACjD,OAAO,IAAI,KAAK,OAAO,SACrB,KAAK,SAAS,IAAI,OAAO,KAAK,OAAO,GAAG,KAAK,IAAyB;OACjE,IAAI,KAAK,OAAO,OACrB,KAAK,MAAM,IAAI,OAAO,KAAK,OAAO,GAAG,KAAK,IAAyB;OAC9D,IAAI,KAAK,OAAO,OAAO;GAC5B,MAAM,UAAU;GAChB,MAAM,SAAS,KAAK,QAAQ,IAAI,QAAQ,CAAC,KAAK;IAAE,MAAM,QAAQ;IAAG,IAAI;IAAG,QAAQ;GAAE;GAClF,IAAI,QAAQ,MAAM,MAAM,OAAO,KAAK,KAAK,IAAI,OAAO,IAAI,QAAQ,CAAC;QAC5D,OAAO,SAAS,KAAK,IAAI,OAAO,QAAQ,QAAQ,CAAC;GACtD,KAAK,QAAQ,IAAI,QAAQ,GAAG,MAAM;GAClC,IAAI,QAAQ,MAAM,QAAQ,QAAQ,MAAM,KAAA,GAAW,KAAK,QAAQ,IAAI,QAAQ,GAAG,QAAQ,CAAC;EAC1F;CACF;CACA,UAAqB;EACnB,OAAO,CAAC,GAAG,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,QAAQ,IAAI,OAAO,WAAA,KAAiB,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;CAC1H;AACF;;AAyBA,MAAM,YAAY,QAAgC;CAChD,MAAM,EAAE,SAAS,SAAS;CAC1B,MAAM,UAAU,QAAQ,QAAQ;CAChC,IAAI,QAAQ,WAAW,IAAI,UAAU,OAAO,GAAG,QAAQ,OAAO,iBAAiB,IAAI;CACnF,MAAM,OAAO,QAAQ,MAAM,QAAQ,IAAI,OAAO,CAAC,IAAI,KAAK;CACxD,IAAI,SAAS,KAAA,GAAW,OAAO,GAAG,KAAK,KAAK;CAC5C,IAAI,IAAI,IAAI,QAAQ,KAAK,QAAQ,IAAI,OAAO,oBAAoB,CAAC,CAAC,CAAC,OAAO,GAAG,OAAO;CACpF,IAAI,SAAS,IAAI,GAAG;EAClB,MAAM,QAAQ,CAAC,GAAG,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,QAAQ,IAAI,OAAO,WAAW,KAAK;EACpF,IAAI,MAAM,WAAW,KAAK,CAAC,MAAM,EAAE,CAAE,SAAS,MAAM,EAAE,CAAE,KAAK,OAAO;CACtE;CACA,MAAM,MAAM,QAAQ,KAAK,QAAQ,IAAI,MAAM,EAAE,CAAC,CAAC,KAAK;CACpD,MAAM,QAAQ,CAAC,GAAG,QAAQ,OAAO,OAAO,CAAC,CAAC,CACvC,QAAQ,UAAU,MAAM,WAAA,KAAiB,CAAC,CAC1C,SAAS,UAAW,MAAM,MAAM,CAAuC,QAAQ,aAAa,SAAS,CAAC,CAAC,CAAC,KAAK,aAAa,SAAS,EAAE,CAAC,CAAC,CACvI,KAAK;CACR,IAAI,MAAM,KAAK,MAAM,IAAI,KAAK,GAAG,OAAO,yBAAyB,MAAM,KAAK,GAAG,EAAE,QAAQ,IAAI,KAAK,GAAG;CACrG,MAAM,WAAW,IAAI,KAAK,OAAO,GAAG,GAAG,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK;CAC3D,KAAK,MAAM,QAAQ,IAAI,QAAQ,OAC7B,KAAK,QAAQ,SAAS,IAAI,IAAI,KAAK,CAAC,EAAA,CAAG,KAAK,MAAM,UAAU,OAAO,gBAAgB,KAAK,SAAS,QAAQ,SAAS,IAAI,IAAI,KAAK,CAAC,EAAA,CAAG,KAAK,GAAG;CAE7I,IAAI,KAAK,MAAM,SAAS,WACjB;OAAA,MAAM,QAAQ,IAAI,QAAQ,OAC7B,KAAK,QAAQ,MAAM,IAAI,IAAI,KAAK,CAAC,EAAA,CAAG,KAAK,MAAM,UAAU,OAAO,yBAAyB,KAAK,QAAQ,QAAQ,MAAM,IAAI,IAAI,KAAK,CAAC,EAAA,CAAG,KAAK,GAAG,KAAK;CAAA;CACtJ,MAAM,QAAQ,IAAI,IAAI,CAAC,GAAG,QAAQ,QAAQ,OAAO,CAAC,CAAC,CAAC,KAAK,WAAW,OAAO,IAAI,CAAC;CAChF,IAAI,MAAM,SAAS,IAAI,QAAQ,MAAM,QAAQ,OAAO,qBAAqB,MAAM,KAAK;CACpF,KAAK,MAAM,CAAC,KAAK,WAAW,QAAQ,SAAS;EAC3C,IAAI,QAAQ,OAAO,OAAO,KAAK,KAAM,OAAO,UAAU,IAAI;EAC1D,IAAI,QAAQ,OAAO,OAAO,SAAS,KAAM,OAAO,UAAU,IAAI;CAChE;CACA,OAAO;AACT;;AAGA,MAAM,SAAS,OAAO,KAAc,WAA2C;CAC7E,MAAM,MAAM,KAAK,IAAI,IAAI;CACzB,IAAI,MAAM,SAAS,GAAG;CACtB,OAAO,QAAQ,QAAQ,KAAK,IAAI,IAAI,KAAK;EACvC,MAAM,MAAM,GAAG;EACf,MAAM,SAAS,GAAG;CACpB;CACA,OAAO;AACT;;AAGA,MAAM,SAAS,OAAO,KAAc,SAAmD;CAErF,MAAM,UAAS,MADM,IAAI,OAAO,KAAK;EAAC;EAAO;EAAM;EAAM;EAAK;EAAM;CAAG,CAAC,EAAA,CAClD,OAAO,MAAM,QAAQ,CAAC,CAAC,GAAG,EAAE,KAAK;CACvD,MAAM,OAAO,OAAO,cAAc,KAAK,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI;CAC9D,MAAM,MAAM,OACT,MAAM,IAAI,CAAC,CACX,QAAQ,SAAS,YAAY,KAAK,IAAI,CAAC,CAAC,CACxC,MAAM,GAAG,CAAC,CAAC,CACX,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC,QAAQ,QAAQ,GAAG,CAAC;CAEjD,MAAM,WADU,MAAM,QAAQ,UAAU;EAAC;EAAM;EAAY;CAA+B,CAAC,EAAA,CAAG,KAAK,CAAC,CAAC,MAAM,IACtF,CAAC,CACnB,QAAQ,SAAS,SAAS,EAAE,CAAC,CAC7B,QAAQ,SAAS,CAAC,KAAK,IAAI,KAAK,MAAM,GAAG,CAAC,CAAC,EAAG,KAAK,CAAC,KAAK,IAAI,KAAK,MAAM,GAAG,CAAC,CAAC,EAAG,CAAC,CAAC,CAClF,QAAQ,SAAS,mEAAmE,KAAK,KAAK,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC;CACrH,OAAO;EAAE,GAAG,KAAK,IAAI;EAAG;EAAM;EAAK;CAAQ;AAC7C;;AAGA,MAAM,QAAQ,OAAO,KAAc,SAAmD;CACpF,SAAS;EAEP,MAAM,OAAO,MAAM,OAAO,qBAAK,IAAI,IAAI;GAAC,GAAG;GAAM,GAAG,IAAI,IAAI,IAAI;GAAG,IAAI,OAAO,QAAQ,CAAC,CAAC,QAAQ,OAAO,EAAE;EAAC,CAAC,CAAC;EAC5G,IAAI,MAAM,KAAK,IAAI;EACnB,IAAI,KAAK,QAAQ,WAAW,KAAK,KAAK,QAAQ,IAAI,MAAM,OAAO;EAC/D,IAAI,IAAI,cAAc,KAAK,MAAM,KAAK,OAAO,GAAG,EAAE,gCAAgC,KAAK,QAAQ,KAAK,IAAI,KAAK,QAAQ;EACrH,MAAM,MAAM,GAAM;CACpB;AACF;;;;;;AAOA,MAAM,SAAS,OAAO,KAAc,SAAkB,MAAgB,WAAmB,WAAoC;CAC3H,MAAM,QAAQ,MAAM,CAAC,gBAAgB,MAAM,SAAS,CAAC,CAAC;CACtD,MAAM,IAAI,MAAM,CAAC,SAAS,GAAG,EAAE,SAAS,eAAe,IAAI,EAAE,CAAC;CAC9D,IAAI,SAAS,IAAI,GAAG;EAClB,MAAM,QAAQ,MAAM,aAAa,WAAW,OAAO,KAAK,CAAC;EACzD,MAAM,QAAQ,QAAQ;GAAC;GAAM;GAAW;GAAW;GAAU,cAAc;GAAS;EAAgB,CAAC;CACvG;CACA,MAAM,OAAO,QAAQ,IAAI,MAAM;CAC/B,IAAI,QAAQ,OAAO,SAAS,KAAA,GAAW,MAAM,IAAI,MAAM,GAAG,KAAK,KAAK,sCAAsC;CAC1G,MAAM,QAAQ,MAAM,cAAc,MAAM,WAAW,OAAO,OAAO,OAAQ,IAAI,KAAK,CAAC;CACnF,MAAM,aAAa,MAAM,QAAQ,QAAQ;EAAC;EAAM;EAAW;EAAO;;EAAmB;EAAM;CAA4B,CAAC,EAAA,CAAG,KAAK;CAChI,MAAM,UAAU,cAAc,MAAM,WAAW,IAAI,OAAO,GAAG,UAAU,IAAI;CAC3E,IAAI,CAAC,MAAM,MAAM,QAAQ,MAAM,OAAO;CACtC,MAAM,QAAQ,QAAQ;EAAC;EAAM;EAAW;EAAW;EAAU;EAAwB;CAAgB,CAAC;CACtG,IAAI,MAAM,MAAM,QAAQ,MAAM,OAAO;CACrC,MAAM,QAAQ,QAAQ;EAAC;EAAM;EAAW;EAAW;EAAU,aAAa;EAAU;CAAgB,CAAC;CACrG,MAAM,IAAI,MAAM;EAAE,MAAM,QAAQ,SAAS,UAAU;EAAM,IAAI;EAAW,KAAA;EAAa,IAAI;EAAW,MAAM;EAAI,OAAO;CAAY,CAAC;CAClI,IAAI,KAAK,MAAM,SAAS,WAAW,MAAM,cAAc,KAAK,SAAS,SAAS;CAC9E,OAAO;AACT;;;;;;AAOA,MAAM,gBAAgB,OAAO,KAAc,SAAkB,cAAqC;CAChG,MAAM,SAAS,KAAK,MAAM,MAAM,QAAQ,QAAQ;EAAC;EAAM;EAAW;EAAO;EAAQ;EAAM,OAAO;EAAU;EAAM;CAAM,CAAC,CAAC;CAGtH,MAAM,OAA0D,CAAC;CACjE,KAAK,MAAM,QAAQ,OAAO,MAAM,QAAQ,SAAS,KAAK,SAAS,sBAAsB,KAAA,CAAS,GAAG;EAC/F,MAAM,OAAO,KAAK,KAAK,YAAY;EACnC,KAAK,KAAK;GAAE;GAAM,KAAK,KAAK,SAAS;GAAM,KAAK,MAAM,IAAI,MAAM,MAAM,WAAW,KAAK,SAAS,MAAM,MAAM;EAAE,CAAC;CAChH;CACA,MAAM,IAAI,IAAI;EAAE,SAAS,cAAc,SAAS;EAAG,OAAO;EAAW;CAAK,CAAC;AAC7E;;;;;;AAOA,MAAM,gBAAgB,OAAO,QAAgC;CAC3D,IAAI,CAAC,eAAe,IAAI,IAAI,GAAG;CAC/B,MAAM,UAAU,IAAI,QAAQ,QAAQ,CAAC,CAAC,QAAQ,QAAQ,CAAC,IAAI,OAAO,IAAI,SAAS,IAAI;CACnF,MAAM,MAAM,QAAQ,KAAK,QAAQ,GAAG,IAAI,KAAK,GAAG,IAAI,GAAG,KAAK,WAAW,OAAO,EAAE,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK;CAC3G,IAAI,QAAQ,IAAI,UAAU;CAC1B,MAAM,OAA0D,CAAC;CACjE,KAAK,MAAM,OAAO,SAAS,KAAK,KAAK;EAAE,MAAM,IAAI;EAAO,KAAK,IAAI;EAAM,KAAK,MAAM,IAAI,IAAI,MAAM,IAAI,MAAO,IAAI,WAAW,IAAI,MAAM,KAAK;CAAE,CAAC;CAC5I,MAAM,IAAI,IAAI,IAAI;EAAE,OAAO;EAAW;CAAK,CAAC;CAC5C,IAAI,WAAW;AACjB;;AAGA,MAAM,eAAe,OAAO,QAAgC;CAC1D,MAAM,OAAO,CAAC,GAAG,IAAI,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,QAAQ,CAAC,IAAI,OAAO,IAAI,SAAS,QAAQ,IAAI,GAAG,OAAO,WAAW,OAAO,OAAO,SAAS,CAAC;CAC9I,MAAM,MAAM,KAAK,KAAK,QAAQ,GAAG,IAAI,KAAK,GAAG,IAAI,GAAG,KAAK,WAAW,OAAO,EAAE,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK;CACxG,IAAI,QAAQ,IAAI,QAAQ;CACxB,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,YAAY,IAAI,OAAO,WAAA,QAAoB,QAAQ,IAAI,OAAO,WAAA,WAAoB,SAAS;EACjG,MAAM,MAAM,MAAM,IAAI,IAAI,MAAM,IAAI,MAAO,IAAI,WAAW,IAAI,MAAM,SAAS;EAC7E,MAAM,KAAK,MAAM,IAAI,IAAI,QAAQ,IAAI,MAAO,GAAG,CAAC;CAClD;CACA,MAAM,IAAI,IAAI,MAAM,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC,CAAC;CACvC,IAAI,SAAS;AACf;;;;;;;AAQA,MAAM,cAAc,QAA4B;CAC9C,MAAM,WAAW,IAAI,QAAQ,QAAQ,CAAC,CAAC,KAAK,SAAS;EAAE,KAAK,IAAI;EAAM,MAAM,IAAI,QAAQ;EAAI,IAAI,IAAI,MAAM;EAAI,SAAS,IAAI,OAAO,wBAAwB;CAAG,EAAE;CAC/J,OAAO,QAAQ,IAAI,IAAI,MAAM,SAAS,UAAU,UAAU,IAAI,QAAQ,OAAO,IAAI;AACnF;;;;;AAMA,MAAM,iBAAiB;;;;;;;;;;;;;AAcvB,MAAM,gBAAgB,OAAO,KAAc,QAAgB,aAAiD;CAC1G,MAAM,UAAU,CAAC,GAAG,IAAI,QAAQ,KAAK,OAAO,CAAC,CAAC,CAC3C,QAAQ,QAAQ,IAAI,OAAO,WAAA,YAAqB,CAAC,IAAI,OAAO,IAAI,OAAO,IAAI,CAAC,CAC5E,KAAK,QAAQ,IAAI,EAAG,CAAC,CACrB,KAAK;CACR,IAAI,QAAQ,WAAW,KAAK,SAAS,WAAW,GAAG,MAAM,IAAI,MAAM,4CAA4C,QAAQ,OAAO,YAAY,SAAS,OAAO,UAAU;CACpK,MAAM,QAAQ,OAAO,WAAW,OAAO;CACvC,MAAM,yBAAS,IAAI,IAAsB;CACzC,KAAK,MAAM,WAAW,UAAU;EAE9B,MAAM,OADQ,QAAQ,IAAI,KAAK,IAAI,QAAQ,GAAG,IAAI,KAAA,MAC5B,MAAM,IAAI,IAAI,MAAM,QAAQ,MAAM,IAAI,WAAW,QAAQ,KAAK,KAAK;EACzF,IAAI,KAAK,IAAI,QAAQ,KAAK,GAAG;EAC7B,OAAO,IAAI,QAAQ,MAAM,CAAC,GAAI,OAAO,IAAI,QAAQ,IAAI,KAAK,CAAC,GAAI,GAAG,CAAC;CACrE;CACA,MAAM,KAAM,MAAM,IAAI,IAAI,IAAI,IAAK;CACnC,MAAM,OAAO;EAAC;EAAQ,QAAQ,KAAK,GAAG;EAAG;EAAQ,OAAO,KAAK,MAAM,KAAK,GAAG,CAAC;CAAC;CAC7E,MAAM,SAAS,MAAM,QAAQ,IAC3B,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,UAAU;EACtC,MAAM,MAAe,CAAC;EACtB,KAAK,MAAM,OAAO,MAAM;GACtB,IAAI,OAAO,MAAM,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ;GACnD,IAAI,KAAK,MAAM,IAAI,IAAI,OAAO,MAAM,KAAK,IAAI,CAAC;EAChD;EACA,OAAO;CACT,CAAC,CACH;CACA,IAAI,CAAC,OAAO,KAAK,MAAM,CAAC,MAAM,SAAS,QAAQ,KAAK,MAAM,OAAO,MAAM,MAAM,IAAI,IAAI,OAAO,MAAM,KAAK,KAAK;CAC5G,MAAM,MAAM,OAAO,KAAK;CACxB,OAAO;EAAE,IAAI,KAAK,IAAI,GAAG,IAAI,KAAK,UAAU,MAAM,EAAE,CAAC;EAAG,IAAI,KAAK,IAAI,GAAG,IAAI,KAAK,UAAU,MAAM,EAAE,CAAC;CAAE;AACxG;;;;;;;AAQA,MAAM,gBAAgB,OAAO,KAAc,WAAmC;CAC5E,MAAM,UAAU,CAAC,GAAG,IAAI,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,QAAQ,IAAI,OAAO,WAAA,YAAqB,CAAC,IAAI,OAAO,IAAI,SAAS,IAAI;CAC5H,IAAI,QAAQ,WAAW,GAAG,MAAM,IAAI,MAAM,cAAc,OAAO,eAAe;CAC9E,MAAM,SAAS,MAAM,QAAQ,IAC3B,QAAQ,IAAI,OAAO,QAAQ;EACzB,MAAM,OAAO,IAAI;EACjB,MAAM,QAAQ,WAAW,YAAY,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI,KAAA;EAC9D,IAAI,UAAU,KAAA,GACZ,IAAI;GACF,OAAO,MAAM,IAAI,IAAI,IAAI,MAAM;IAAE,IAAI;IAAU,KAAK;IAAO;GAAO,CAAC;EACrE,QAAQ,CAER;EAEF,MAAM,MAAM,MAAM,IAAI,IAAI,MAAM,MAAM,IAAI,WAAW,IAAI,MAAM,MAAM;EACrE,IAAI,KAAK,IAAI,IAAI,MAAM,GAAG;EAC1B,OAAO,IAAI,IAAI,IAAI,MAAM;GAAE,IAAI;GAAU;GAAK;EAAO,CAAC;CACxD,CAAC,CACH;CACA,OAAO;EAAE,IAAI,KAAK,IAAI,GAAG,OAAO,KAAK,UAAU,MAAM,EAAE,CAAC;EAAG,IAAI,KAAK,IAAI,GAAG,OAAO,KAAK,UAAU,MAAM,EAAE,CAAC;CAAE;AAC9G;;AAGA,MAAM,SAAS,OAAO,KAAc,MAAY,UAA8B,eAA+D;CAC3I,MAAM,EAAE,KAAK,SAAS,cAAc;CACpC,MAAM,SAAS,SAAS;CACxB,MAAM,QAAQ,SACZ,KAAK,KAAK,QACR,IAAI,WAAW,eAAe,SAAS,CAAC,CAAC,WAAW,KAAK,OAAO,GAAG,CAAC,CAAC,WAAW,MAAM,OAAO,IAAI,CAAC,CAAC,WAAW,SAAS,OAAO,IAAI,OAAO,CAAC,CAC5I;CACF,MAAM,QAAQ,OAAO,UAA+E;EAClG,IAAI,UAAU,WAAW,MAAM,IAAI,MAAM,qDAAqD;EAC9F,IAAI,UAAU,WAAW,MAAM,IAAI,MAAM,iDAAiD;EAC1F,IAAI,UAAU,UAAU,OAAO;GAAE,MAAM,OAAO;GAAM,KAAK,MAAM,IAAI,MAAM,OAAO,MAAM,WAAW,OAAO,KAAK,KAAK;GAAG,KAAK;EAAK;EAC/H,IAAI,UAAU,SAAS;GACrB,MAAM,QAAQ,CAAC,GAAG,IAAI,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,QAAQ,IAAI,OAAO,WAAA,WAAoB,CAAC,IAAI,GAAG;GAClG,OAAO;IAAE,MAAM,MAAM;IAAO,KAAK,MAAM,IAAI,MAAM,MAAM,MAAO,WAAW,MAAM,MAAM,KAAK;IAAG,KAAK;GAAM;EAC1G;EACA,MAAM,SAAS,QAAQ,MAAM,MAAM,QAAQ,MAAM;EACjD,MAAM,QAAQ,KAAK,MACjB,MAAM,QAAQ,QAAQ;GAAC;GAAM;GAAe;GAAO;GAAQ;GAAM;GAAsB;GAAoB,iBAAiB;GAAU;GAAM;EAAM,CAAC,CACrJ;EACA,OAAO;GAAE,MAAM;GAAQ,KAAK,MAAM,IAAI,MAAM,QAAQ,eAAe,MAAM,MAAM,EAAE,CAAE,SAAS,MAAM,YAAY;GAAG,KAAK;EAAK;CAC7H;CACA,QAAQ,KAAK,IAAb;EACE,KAAK,WAAW;GACd,MAAM,KAAK,MAAM,IAAI,IAAI;GACzB,IAAI,KAAK,KAAK,SAAS,uBAAwB,GAAG,IAAI,WAAW;GACjE,MAAM,OAAO,QAAQ,QAAQ,KAAK,KAAK,IAAI,CAAC;GAC5C,IAAI,KAAK,eAAe,MAAM;IAC5B,WAAW,KAAK,KAAK,OAAO,UAAmB,IAAI,IAAI,aAAa,KAAK,KAAK,KAAK,GAAG,EAAE,IAAI,OAAO,KAAK,GAAG,CAAC,CAAC;IAC7G,OAAO;KAAE;KAAI,IAAI;IAAG;GACtB;GACA,MAAM;GACN,OAAO;IAAE;IAAI,IAAI,MAAM,IAAI,IAAI;GAAE;EACnC;EACA,KAAK,UAAU;GACb,IAAI,KAAK,WAAW,WAAW,OAAO,cAAc,KAAK,KAAK,MAAM;GACpE,MAAM,KAAK,MAAM,MAAM,KAAK,MAAM;GAClC,OAAO,IAAI,IAAI,GAAG,MAAM;IAAE,IAAI;IAAU,KAAK,GAAG;IAAK,QAAQ,KAAK;GAAO,CAAC;EAC5E;EACA,KAAK,SAAS;GACZ,MAAM,KAAK,MAAM,MAAM,KAAK,MAAM;GAClC,MAAM,QAAQ,IAAI,IAAI,GAAG,MAAM;IAAE,IAAI;IAAS,KAAK,GAAG;IAAK,IAAI,QAAQ,KAAK,MAAM,IAAI;GAAE,CAAC;GACzF,WAAW,KAAK,KAAK;GACrB,MAAM,MAAM,MAAM,IAAI,IAAI;GAC1B,OAAO;IAAE,IAAI;IAAK,IAAI;GAAI;EAC5B;EACA,KAAK,UAAU;GACb,IAAI,KAAK,WAAW,WAAW,OAAO,cAAc,KAAK,KAAK,QAAQ,QAAQ;GAC9E,MAAM,KAAK,MAAM,MAAM,KAAK,MAAM;GAClC,IAAI,QAAsB;GAC1B,KAAK,MAAM,WAAW,UAAU;IAC9B,MAAM,QAAQ,MAAM,IAAI,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC,KAAK,QAAQ,QAAQ,EAAE,CAAC;IACzE,UAAU;GACZ;GACA,OAAO;EACT;EACA,KAAK,QAAQ;GACX,MAAM,KAAK,MAAM,MAAM,KAAK,MAAM;GAClC,OAAO,KAAK,KACR,IAAI,IAAI,GAAG,MAAM;IAAE,IAAI;IAAS,MAAM,SAAS,GAAG,IAAI;IAAqB,MAAM;GAAI,CAAC,IACtF,IAAI,IAAI,GAAG,MAAM;IAAE,IAAI;IAAU,MAAM,SAAS,GAAG,IAAI;GAAoB,CAAC;EAClF;EACA,KAAK,OAAO;GACV,MAAM,KAAK,MAAM,MAAM,KAAK,MAAM;GAClC,MAAM,SAAS,MAAM,IAAI,SAAS,GAAG,MAAM,GAAG,GAAG;GACjD,OAAO,IAAI,IAAI,GAAG,MAAM;IAAE,IAAI;IAAS,MAAM,GAAG,OAAO;IAAW,MAAM,KAAK;GAAI,CAAC;EACpF;EACA,KAAK,SAAS;GACZ,MAAM,KAAK,MAAM,MAAM,KAAK,MAAM;GAClC,OAAO,IAAI,IAAI,GAAG,MAAM;IAAE,IAAI;IAAS,KAAK,GAAG;IAAK,IAAI,QAAQ,KAAK,MAAM,IAAI;GAAE,CAAC;EACpF;EACA,KAAK;GACH,MAAM,MAAM,QAAQ,KAAK,MAAM,IAAI,CAAC;GACpC,OAAO;EACT,KAAK,SAAS;GAEZ,MAAM,SAAS,KAAK,MAAM,KAAK,CAAC,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,EAAG;GACjE,MAAM,KAAK,MAAM,IAAI,IAAI;GACzB,MAAM,QAAQ,MAAM,CAAC,MAAM,CAAC;GAC5B,OAAO;IAAE;IAAI,IAAI,MAAM,IAAI,IAAI;GAAE;EACnC;CACF;AACF;;AAGA,MAAM,QACJ,KACA,KACA,UACY;CACZ,IAAI,SAAS,GAAG,MAAM,MAAM,OAAO;CACnC,MAAM,OAAO,IAAI,KAAK,QAAQ;CAC9B,KAAK,SAAS,WAAW,SAAS,uBAAuB,IAAI,WAAW,MAAM;EAC5E,MAAM,MAAM,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAO,GAAG;EAE/C,QADY,QAAQ,KAAA,IAAY,IAAI,IAAI,GAAG,QAAQ,KAAK,WAAW,MAAM,OAAO,IAAI,CAAC,MACvE,IAAI,SAAS,IAAI,IAAI,OAAO,GAAG,KAAK;CACpD;CACA,IAAI,IAAI,WAAW,QAAQ;EAAC;EAAgB;EAAc;EAAY;CAAgB,CAAC,CAAC,SAAS,IAAI,MAAM,IAAI,KAAK,aAAa,SAAS,IAAI,KAAK,aAAa,QAC9J,OAAO,SAAS,cAAc,SAAS,eACnC,IAAI,SAAS,MAAM,YAAY,CAAC,IAAI,QAAQ,KAAK,IAAI,QAAQ,GAAG,CAAC,IACjE,CAAC,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAO,GAAG;CAC1C,IAAI,SAAS,oBAAoB,SAAS,WAAW,OAAO,IAAI,SAAS,OAAO,YAAY,CAAC,IAAI,QAAQ,KAAK,IAAI,QAAQ,GAAG,CAAC;CAC9H,IAAI,SAAS,aAAa,OAAO,MAAM,MAAM,SAAS,KAAK,OAAO,WAAW,KAAK,aAAa,KAAK;CACpG,OAAO;AACT;;AAGA,MAAM,aAAa,MAAgB,UAA8B,QAAwB,UACvFC,QAAY,IAAI,IAAK,SAAS,MAAM,YAAY,MAAM,MAAM,SAAS,KAAK,OAAO,SAAS,KAAK,YAAY,QAAQ,OAAO,KAAK,WAAW,IAAI,CAAC,KAAK,SAAU;;;;;;AAOhK,MAAM,UAAU,OAAO,KAAc,KAAa,OAAe,QAAiB,SAAwC;CACxH,MAAM,EAAE,KAAK,MAAM,SAAS,cAAc;CAC1C,MAAM,OAAO,OAAO,IAAI;CACxB,MAAM,SAAS,MAAM,OAAO,KAAK,GAAO;CACxC,MAAM,SAAS,KAAa,WAA4B;EACtD;EAAK;EAAO;EAAQ;EAAO,KAAK;EAAO,MAAM;GAAE,OAAO;GAAM,MAAM;GAAM,QAAQ;EAAK;EAAG,QAAQ;EAAM,QAAQ;EAAM,UAAU,CAAC;EAAG,MAAM;EAAK,OAAO,IAAI,KAAK;EAAG,SAAS,CAAC;CAC5K;CACA,IAAI,WAAW,MAAM,OAAO,MAAM,6BAA6B,UAAU,MAAM,IAAI,IAAI,CAAC;CACxF,MAAM,aAAa,GAAG;CACtB,MAAM,cAAc,GAAG;CACvB,MAAM,MAAM,KAAK,IAAI;CACrB,IAAK,MAAM,OAAO,KAAK,GAAO,MAAO,MAAM,OAAO,MAAM,0DAA0D,MAAM,IAAI,IAAI,CAAC;CACjI,MAAM,WAAW,WAAW,GAAG;CAC/B,MAAM,WAAW,IAAI,IAAI,IAAI,QAAQ,QAAQ,CAAC,CAAC,KAAK,QAAQ,CAAC,IAAI,MAAM,IAAI,GAAG,QAAQ,KAAK,WAAW,MAAM,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC;CAC3H,MAAM,SAAS,SAAS,IAAI,KAAK;EAAC;EAAO;EAAa;EAAkB;EAA2B;CAAS,CAAC,CAAC,SAAS,KAAK,QAAQ,IAAI,IAAI,OAAO,SAAS;CAC5J,MAAM,SAAS,KAAK,aAAa,SAAS,KAAK,QAAQ,SAAS,mBAAoB,QAAQ,MAAM,MAAM,QAAQ,MAAM,KAAO;CAG7H,MAAM,UAAU,MAAM,IAAI,iBAAiB,IAAI,WAAW,EAAE;CAC5D,IAAI,KAAK;CACT,KAAK,MAAM,OAAO,OAAO,KAAK,IAAI,OAAO,GAAG,OAAO,IAAI,QAAQ;CAC/D,MAAM,QAAQ,MAAM,IAAI,IAAI;CAC5B,MAAM,aAAsC,CAAC;CAC7C,IAAI,QAAuB;CAC3B,IAAI,SAAwB;CAC5B,IAAI,KAAK,QAAQ,SAAS,YAAY,IAAI,WAAW,KAAK,IAAI,WAAW;CACzE,MAAM,QAAkB,CAAC;CACzB,MAAM,YAAY,OAAO,QAAQ,OAAO,CAAC,CAAC,QAAQ,GAAG,WAAW,QAAQ,CAAC;CACzE,IAAI,UAAU,SAAS,GAAG,MAAM,KAAK,sEAAsE,UAAU,KAAK,CAAC,MAAM,WAAW,GAAG,MAAM,MAAM,MAAM,CAAC,CAAC,KAAK,IAAI,GAAG;CAC/K,KAAK,MAAM,QAAQ,KAAK,QAAQ;EAC9B,MAAM,QAAQ,MAAM,OAAO,KAAK,MAAM,UAAU,UAAU;EAC1D,IAAI,UAAU,QAAQ,UAAU,MAAM,QAAQ,MAAM;CACtD;CACA,IAAI,OAAsB;CAC1B,IAAI,KAAK,SAAS,MAAM;EACtB,MAAM,MAAM,MAAM,IAAI,IAAI;EAC1B,MAAM,MAAM,KAAK,IAAI,GAAG,QAAQ,KAAK,MAAM,IAAI,KAAK,OAAO,SAAS,KAAK,CAAC;EAC1E,KAAK,MAAM,QAAQ,KAAK,MACtB,IAAI;GACF,MAAM,QAAQ,MAAM,OAAO,KAAK,MAAM,UAAU,UAAU;GAC1D,IAAI,UAAU,QAAQ,SAAS,MAAM,OAAO,MAAM;EACpD,SAAS,OAAO;GACd,MAAM,KAAK,YAAY,KAAK,GAAG,WAAW,OAAO,KAAK,GAAG;EAC3D;CAEJ;CACA,MAAM,UAAU,MAAM,QAAQ,WAAW,UAAU;CACnD,KAAK,MAAM,QAAQ,SACjB,IAAI,KAAK,WAAW,eAAe,OAAO,KAAK,UAAU,YAAY,KAAK,UAAU,QAAQ,QAAQ,KAAK,OAAO,SAAU,KAAK,MAAgB;CAGjJ,MAAM,QAAQ,KAAK,IAAI,IAAI,OAAU,QAAQ,KAAK,IAAI,OAAO,IAAI,IAAI,QAAQ,KAAK,IAAI,SAAS,IAAI;CACnG,MAAM,MAAM,GAAI;CAChB,OAAO,KAAK,IAAI,IAAI,SAAS,CAAC,KAAK,KAAK;EAAE;EAAQ;EAAU;CAAS,GAAG,IAAI,KAAK,GAAG,MAAM,MAAM,GAAG;CAEnG,MAAM,QAAQ,eAAe,MAAM;EAAE;EAAO,MAAM;GAAE;GAAO;GAAM;EAAO;EAAG,QAAQ,UAAU,MAAM,UAAU,QAAQ,IAAI,KAAK;EAAG;EAAU,OAAO,IAAI;CAAM,CAAC;CAC7J,IAAI,UAAU,MAAM;EAClB,MAAM,OAAO,KAAK,IAAI,QAAS,MAAM,IAAI,IAAI,GAAI,QAAQ,KAAK,IAAI,CAAC;EACnE,IAAI,OAAO,GAAG,MAAM,MAAM,IAAI;CAChC;CACA,MAAM,MAAM,MAAM,IAAI,IAAI;CAC1B,MAAM,WAAW,KAAK,IAAI,KAAK;CAC/B,KAAK,MAAM,QAAQ,KAAK,SACtB,IAAI;EACF,MAAM,OAAO,KAAK,MAAM,UAAU,UAAU;CAC9C,SAAS,OAAO;EACd,MAAM,KAAK,eAAe,KAAK,GAAG,WAAW,OAAO,KAAK,GAAG;CAC9D;CAEF,IAAI,WAAW,KAAK,IAAI;CACxB,KAAK,MAAM,QAAQ,OAAO,IAAI,IAAI,KAAK,MAAM;CAC7C,MAAM,QAAQ,IAAI,KAAK;CACvB,MAAM,UAAU,UAAU,MAAM,UAAU,QAAQ,KAAK;CAEvD,IADkB,MAAM,MAAM,SAAS,KAAK,OAAO,SAAU,KAAK,KAAK,CAA2B,MAAM,WAAW,OAAO,KAAK,CAAC,CACpH,GAAG;EAEb,MAAM,QAAQ,QAAQ;GAAC;GAAM;GAAW;GAAW;GAAW;EAAsB,CAAC;EACrF,MAAM,QAAQ,QAAQ;GAAC;GAAM;GAAW;GAAW;GAAU;GAAwB;EAAgB,CAAC;CACxG;CACA,IAAI,KAAK,QAAQ,SAAS,cAAc,MAAM,UAAU,GAAG;CAC3D,IAAI,IAAI,KAAK,SAAS,YAAY,OAAO,QAAQ,IAAI,IAAI,MAAM,KAAK,EAAE,IAAI,WAAW,0BAA0B,IAAI;CACnH,OAAO;EACL;EAAK;EAAO;EAAQ;EAAO;EAAK,MAAM;GAAE;GAAO;GAAM;EAAO;EAAG,QAAQ;EAAS;EAAQ;EAAU,MAAM;EAAM;EAAO,SAAS,EAAE,GAAG,IAAI,QAAQ;EAAG;CACpJ;AACF;AAEA,MAAM,OAAO,OAAuB,KAAK,MAAM,EAAE,IAAI;;AAGrD,MAAM,YAAY,OAAO,QAAgC;CACvD,MAAM,OAAO,KAAK,GAAO;CACzB,MAAM,UAAU,IAAI,QAAQ,QAAQ;CACpC,MAAM,yBAAS,IAAI,IAAsB;CACzC,KAAK,MAAM,OAAO,SAAS,OAAO,IAAI,IAAI,QAAQ,IAAI,CAAC,GAAI,OAAO,IAAI,IAAI,QAAQ,EAAE,KAAK,CAAC,GAAI,IAAI,IAAI,CAAC;CACvG,MAAM,UAAU,CAAC,GAAG,OAAO,OAAO,CAAC,CAAC,CAAC,MAAM,SAAS,KAAK,SAAS,CAAC;CACnE,IAAI,YAAY,KAAA,KAAa,OAAO,OAAO,IAAI,QAAQ,MAAM,SAAS,GACpE,MAAM,IAAI,QAAQ,QAAQ;EAAC;EAAM,IAAI;EAAW;EAAU;EAAO,QAAQ;EAAK;CAAa,CAAC;AAChG;;AAGA,MAAa,eAAe,KAAa,WAAiC;CACxE,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;CAClC,MAAM,SAAyB;EAAE,QAAQ;EAAQ,GAAG;CAAO;CAC3D,cAAc,KAAK,KAAK,GAAG,KAAK,OAAO,KAAK,IAAI,EAAE,MAAM,GAAG,KAAK,UAAU,MAAM,CAAC;CACjF,MAAM,EAAE,SAAS,SAAS;CAC1B,cACE,KAAK,KAAK,gBAAgB,GAC1B,GAAG,KAAK,UAAU;EAChB,IAAI,KAAK;EACT,MAAM,KAAK;EACX,MAAM,KAAK;EACX,SAAS,KAAK,QAAQ;EACtB,YAAY,KAAK,WAAW;EAC5B,UAAU,QAAQ;EAClB,SAAS,QAAQ;EACjB,QAAQ,QAAQ;EAChB,QAAQ,QAAQ;EAChB,QAAQ,QAAQ;EAChB,KAAK,QAAQ;CACf,CAAC,EAAE,KACH,EAAE,MAAM,IAAI,CACd;AACF;;;;;;;AAQA,MAAa,eAAe,OAAO,OAA4B,YAAmD;CAChH,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,QAAQ,MAAM,SAAS;CAC7B,MAAM,aAAa,MAAM,SAAS,WAAW;CAE7C,MAAM,YAAY,MAAM,MAAM,SAAS,QAAQ,IAAI,MAAM,MAAM,IAAI,MAAM,QAAQ;EAAE,MAAM;EAAW,MAAM;CAAU,CAAC,IAAI,KAAA;CAEzH,MAAM,UAAU,IAAI,IAAI,MAAM,SAAS,SAAU,KAAK,SAAS,KAAA,IAAY,CAAC,IAAI,CAAC,KAAK,KAAK,MAAM,CAAE,CAAC;CACpG,MAAM,yBAAS,IAAI,IAAuC;CAC1D,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,SAAS,WAAW,YAAY,OAAO,CAAC,GAAG,OAAO,IAAI,MAAM,MAAM,SAAS,aAAa,KAAK,CAAC;CAGtI,MAAM,SAAiB;EAAE,KAAK;EAAO,OAAO;EAAY,MAAM;EAAW,MADvE,QAAQ,SAAS,IAAI,KAAA,IAAY;GAAE,OAAO,OAAO,IAAI,OAAO;GAAI,OAAO,OAAO,IAAI,OAAO;GAAI,KAAK,OAAO,IAAI,KAAK;GAAG,SAAS,OAAO,IAAI,SAAS;EAAE;CAC5D;CAE1F,MAAM,yBAAS,IAAI,IAAwB;CAC3C,KAAK,MAAM,QAAQ,OAAO,OAAO,IAAI,WAAW,IAAI,GAAG,CAAC,GAAI,OAAO,IAAI,WAAW,IAAI,CAAC,KAAK,CAAC,GAAI,IAAI,CAAC;CACtG,MAAM,UAA4B,CAAC;CACnC,KAAK,MAAM,CAAC,KAAK,UAAU,QAAQ;EACjC,MAAM,0BAAU,IAAI,IAAwF;EAC5G,KAAK,IAAI,MAAM,GAAG,MAAM,cAAc,OAAO,GAAG;GAC9C,MAAM,IAAI,SAAS,OAAO,IAAI,GAAG,OAAO,MAAM,EAAE,CAAE,MAAM,MAAM,EAAE,CAAE,OAAO;GACzE,MAAM,MAAM,KAAK,QAAQ,QAAQ,KAAK,OAAO,GAAG,mBAAmB,GAAG,EAAE,IAAI;GAC5E,MAAM,OAAO,MAAM,EAAE,CAAE;GACvB,QAAQ,IAAI,WAAW,EAAE,KAAK,IAAI,EAAE,UAAU,KAAK,IAAI,EAAE,QAAQ,UAAU,SAAS,KAAA,IAAY,KAAK,WAAW,KAAK,UAAU;GAC/H,MAAM,UAAU,MAAM,cAAc,GAAG,GAAG;GAC1C,IAAI,MAAsB;GAC1B,IAAI,SAAsC;GAC1C,IAAI;IACF,MAAM,QAAQ,KAAK,KAAK;IACxB,MAAM,QAAQ,KAAK,UAAU;IAC7B,IAAI,cAAc,KAAA,KAAa,MAAM,MAAM,SAAS,QAAQ,IAAI,MAAM,MAAM,GAAG,MAAM,QAAQ,KAAK,SAAS;IAC3G,IAAI,SAAS,KAAA,GAAW;KACtB,IAAI,OAAO,SAAS,KAAA,GAAW,MAAM,IAAI,MAAM,GAAG,MAAM,EAAE,CAAE,KAAK,iCAAiC;KAClG,KAAK,MAAM,QAAQ,YAAY,KAAK,SAAS;MAC3C,MAAM,OAAO,OAAO,KAAK;MACzB,IAAI,SAAS,KAAA,GAAW,MAAM,IAAI,MAAM,GAAG,MAAM,EAAE,CAAE,KAAK,yBAAyB,KAAK,UAAU;MAClG,MAAM,QAAQ,KAAK,IAAI;KACzB;KACA,MAAM,QAAQ,KAAK,IAAI;KACvB,MAAM,YAAY,SAAS,KAAK,QAAQ,OAAO,IAAI;KACnD,QAAQ,IAAI,WAAW,KAAK,OAAO,gBAAgB,KAAK,OAAO,KAAK,IAAI,IAAI,SAAS,GAAG,IAAI,GAAG,GAAG;IACpG;IACA,MAAM,MAAM,YAAY,SAAS,KAAK;IACtC,MAAM,EAAE,qBAAqB,MAAM,OAAO;IAC1C,MAAM,SAAS,cAAc,iBAAiB;KAC5C,YAAY,MAAoB;MAC9B,KAA0D,WAAW,UAAU;MAC/E,OAAO;KACT;IACF;IACA,SAAS,MAAM,IAAI,OAAO,KAAK,CAAC,CAAC,YAAY,MAAM,CAAC,CAAC,gBAAgB,MAAM,CAAC,CAAC,YAAY,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,MAAM;IACtH,MAAM,WAAW,MAAM,QAAQ,SAAS;IACxC,MAAM,uBAAO,IAAI,IAAY,CAAC,GAAG,QAAQ,KAAK,CAAC;IAC/C,KAAK,MAAM,QAAQ,OAAO;KACxB,MAAM,YAAY,YAAY,IAAI;KAClC,QAAQ,IAAI,GAAG,KAAK,KAAK,IAAI,KAAK,GAAG,iBAAiB,KAAK;KAC3D,MAAM,UAAU,IAAI,QAAQ;KAC5B,MAAM,MAAM,IAAI,OAAO,QAAQ,GAAG;KAClC,MAAM,QAAsB,CAAC;KAC7B,IAAI;MACF,MAAM,YAAY,MAAM,OAAO,KAAK,SAAS,MAAM,WAAW,MAAM;MACpE,KAAK,MAAM,QAAQ,CAAC,GAAG,IAAI,IAAI,GAAG,OAAO,QAAQ,CAAC,CAAC,QAAQ,OAAO,EAAE,CAAC,GAAG,KAAK,IAAI,IAAI;MACrF,MAAM,MAAe;OAAE;OAAK;OAAS;OAAM;OAAW;OAAS;OAAW;OAAO;OAAQ,MAAM,QAAQ,QAAQ;OAAK,KAAK,QAAQ;OAAK,SAAS;OAAG,QAAQ;OAAI,UAAU;OAAI,UAAU,KAAK,IAAI;OAAU,sBAAM,IAAI,IAAI;MAAE;MACzN,MAAM,QAAQ,KAAK,KAAK,KAAK,OAAO,YAAY,KAAK,MAAM,KAAK,OAAO,gBAAgB,KAAK,OAAO,iBAAiB,IAAI,IAAI;MAC5H,MAAM,OAAkB,CAAC,MAAM,QAAQ,KAAK,KAAK,IAAI,MAAM,IAAI,CAAC;MAChE,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,SAAS,GAAG,KAAK,KAAK,MAAM,QAAQ,KAAK,KAAK,OAAO,OAAO,IAAI,CAAC;MACpG,MAAM,QAAQ,QAAQ,IAAI,KAAK,IAAI,KAAK;OAAE,MAAM,CAAC;OAAG,WAAW,CAAC;OAAG,OAAO,CAAC;MAAE;MAC7E,MAAM,KAAK,KAAK,GAAG,IAAI;MACvB,MAAM,UAAU,KAAK;OACnB;OACA,UAAU;OACV,SAAS;OACT,QAAQ;QACN,KAAK;QACL,OAAO;QACP,GAAI,QAAQ,IAAI,MAAM,SAAS,EAAE,MAAM,UAAW,IAAI,CAAC;QACvD,GAAI,KAAK,SAAS,KAAA,IAAY,CAAC,IAAI,OAAO,YAAY,YAAY,KAAK,KAAK,OAAO,CAAC,KAAK,SAAS,CAAC,SAAS,QAAQ,aAAa,KAAK,CAAC,CAAC;OAC1I;MACF,CAAC;MACD,MAAM,MAAM,KAAK,GAAG,KAAK;MACzB,QAAQ,IAAI,KAAK,MAAM,KAAK;MAC5B,UAAU,KAAK,QAAQ,KAAK,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;MAC3D,cAAc,KAAK,QAAQ,KAAK,WAAW,GAAG,KAAK,KAAK,IAAI,EAAE,MAAM,IAAI,MAAM,GAAG,KAAK,UAAU;OAAE;OAAM;OAAM;OAAU;MAAM,CAAC,CAAC;KAClI,UAAU;MACR,IAAI;MACJ,MAAM,IAAI,MAAM,IAAI,CAAC,CAAC,YAAY,KAAA,CAAS;MAC3C,MAAM,IAAI,IAAI,IAAI,CAAC,CAAC,YAAY,KAAA,CAAS;MACzC,MAAM,IAAI,IAAI,IAAI,CAAC,CAAC,YAAY,KAAA,CAAS;MACzC,MAAM,IAAI,UAAU;MACpB,MAAM,QAAQ,QAAQ;OAAC;OAAU;OAAa;OAAW;OAAe;MAAgB,CAAC,CAAC,CAAC,OAAO,UAAmB,QAAQ,IAAI,KAAK,OAAO,KAAK,GAAG,CAAC;KACxJ;IACF;GACF,UAAU;IACR,IAAI,WAAW,MAAM,MAAM,OAAO,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,YAAY,KAAA,CAAS;IAC5E,IAAI,QAAQ,MAAM,MAAM,IAAI,MAAM;IAClC,MAAM,QAAQ,OAAO;IACrB,QAAQ,IAAI,WAAW,EAAE,KAAK,SAAS;GACzC;EACF;EACA,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,QAAQ,QAAQ,IAAI,KAAK,IAAI;GACnC,IAAI,UAAU,KAAA,GAAW;GACzB,MAAM,SAAyB;IAAE;IAAM,GAAG;IAAO,SAAS,UAAU,MAAM,MAAM,IAAI;IAAG,qBAAI,IAAI,KAAK,EAAA,CAAE,YAAY;GAAE;GACpH,YAAY,QAAQ,KAAK,MAAM;GAC/B,OAAO,KAAK,QAAQ,KAAK,WAAW,GAAG,KAAK,KAAK,IAAI,EAAE,WAAW,GAAG,EAAE,OAAO,KAAK,CAAC;GACpF,KAAK,IAAI,MAAM,GAAG,MAAM,cAAc,OAAO,GAAG,OAAO,KAAK,QAAQ,KAAK,WAAW,GAAG,KAAK,KAAK,IAAI,EAAE,MAAM,IAAI,MAAM,GAAG,EAAE,OAAO,KAAK,CAAC;GACzI,QAAQ,IAAI,GAAG,KAAK,KAAK,IAAI,OAAO,QAAQ,SAAS,IAAI,OAAO,QAAQ,KAAK;GAC7E,QAAQ,KAAK,MAAM;EACrB;CACF;CACA,OAAO;AACT;;AAGA,MAAM,YAAY,MAAgB,eAAe,MAAc;CAC7D,MAAM,OAAO,KAAK,QAAQ,SAAS,KAAA,IAAY,IAAI,QAAQ,KAAK,QAAQ,MAAM,GAAG;CACjF,MAAM,UAAkC;EACtC,oBAAoB;EACpB,OAAO;EACP,aAAa,OAAO;EACpB,gBAAgB,OAAO;EACvB,SAAS,OAAO;EAChB,wBAAwB,OAAO;EAC/B,MAAM,OAAO;CACf;CAEA,MAAM,QAAQ,iBAAiB,IAAI;CACnC,MAAM,WAAW,UAAU,OAAO,IAAI,OAAO,QAAQ,OAAO,GAAG;CAE/D,MAAM,SAAS,KAAK,WAAW,SAAS,UAAU,KAAK,WAAW,SAAS,OAAO,QAAQ,KAAK,WAAW,KAAK,IAAI,GAAG,IAAI,IAAI;CAC9H,MAAM,OAAO,KAAK,KAAK,IAAI,UAAU,QAAQ,QAAQ,KAAK,QAAQ,UAAU,KAAK,aAAa,QAAQ,OAAO,KAAK,KAAK,QAAQ,KAAK,IAAI,SAAS,GAAG,EAAE;CAGtJ,QADc,KAAK,SAAS,KAAA,IAAY,KAAK,OAC9B,gBAAgB,KAAK,OAAO,gBAAgB;AAC7D;;;;;;AAOA,MAAa,iBAAiB,MAAgB,eAAe,OAAiB;CAAE,OAAO,SAAS,MAAM,YAAY;CAAG,MAAM;AAAI;;;;;;AAO/H,MAAa,WAA+B,OAAqB,OAAe,UAAuB;CACrG,IAAI,EAAE,OAAO,UAAU,KAAK,KAAK,OAAO,UAAU,KAAK,KAAK,SAAS,KAAK,SAAS,KAAK,QAAQ,QAC9F,MAAM,IAAI,WAAW,SAAS,MAAM,MAAM,MAAM,gBAAgB;CAClE,MAAM,QAAQ,SAA2B,GAAG,KAAK,KAAK,GAAG,QAAQ,IAAI,MAAM,SAAS,KAAK,QAAQ,IAAI;CACrG,MAAM,UAAU,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,GAAG,MAAO,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,CAAE;CAChG,MAAM,QAAQ,QAAQ,QAAQ,KAAK,SAAS,MAAM,SAAS,IAAI,GAAG,CAAC;CACnE,MAAM,MAAW,CAAC;CAClB,IAAI,SAAS;CACb,KAAK,MAAM,QAAQ,SAAS;EAC1B,MAAM,SAAS,SAAS,SAAS,IAAI,IAAI;EACzC,IAAI,KAAK,IAAI,QAAQ,GAAG,KAAK,MAAO,SAAS,QAAS,KAAK,CAAC,MAAM,OAAO,IAAI,KAAK,IAAI;EACtF,UAAU,SAAS,IAAI;CACzB;CACA,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["s","seconds","deletesAPod"],"sources":["../src/backends.ts","../src/scenario.ts","../src/tools.ts","../src/cluster.ts","../src/compose.ts","../src/eds.ts","../src/faults.ts","../src/image.ts","../src/istio.ts","../src/lds.ts","../src/conntrack.ts","../src/lab.ts","../src/manifests.ts","../src/predict.ts","../src/rules.ts","../src/run.ts"],"sourcesContent":["// The parser of the rules of kube-proxy. The agent of each node reads the rules with it, and the\n// image copies this file, thus it imports nothing.\n\n/** A service rule names the Service whose backends the agent reads: its namespace, its name, and its ClusterIP and port for IPVS. */\nexport interface ServiceRule {\n readonly mode: \"iptables\" | \"ipvs\" | \"nftables\";\n readonly ns: string;\n readonly svc: string;\n readonly ip: string;\n readonly port: number;\n}\n\nconst hexIp = (hex: string): string =>\n [0, 2, 4, 6].map((at) => parseInt(hex.slice(at, at + 2), 16)).join(\".\");\n\n/**\n * This function gives the backends of a Service in a dump of the rules of kube-proxy, as\n * `ip:port`, sorted. A backend of IPVS with the weight zero takes no new connection, thus it is\n * not a backend.\n * - iptables: the `KUBE-SVC` chain of the Service jumps to a `KUBE-SEP` chain for each backend, and that chain does the DNAT.\n * - nftables: the `service-` chain of the Service does the DNAT to each backend, or it names an `endpoint-` chain for each one.\n * - IPVS: `/proc/net/ip_vs` has the line of the virtual service, then a line for each real server.\n */\nexport const backendsOf = (mode: ServiceRule[\"mode\"], text: string, rule: Pick<ServiceRule, \"ns\" | \"svc\" | \"ip\" | \"port\">): string[] => {\n const out = new Set<string>();\n const lines = text.split(\"\\n\");\n if (mode === \"iptables\") {\n const seps = new Set<string>();\n for (const line of lines) {\n if (!line.startsWith(\"-A KUBE-SVC-\") || !line.includes(`${rule.ns}/${rule.svc}:`)) continue;\n const jump = / -j (KUBE-SEP-\\S+)/.exec(line);\n if (jump !== null) seps.add(jump[1]!);\n }\n for (const line of lines) {\n const chain = /^-A (KUBE-SEP-\\S+) /.exec(line);\n if (chain === null || !seps.has(chain[1]!)) continue;\n const to = / --to-destination (\\S+)/.exec(line);\n if (to !== null) out.add(to[1]!);\n }\n } else if (mode === \"nftables\") {\n // a chain name with a slash can print in quotes: `chain \"service-…\" {` and `goto \"endpoint-…\"`\n let inside = false;\n const ours = `-${rule.ns}/${rule.svc}/`;\n for (const line of lines) {\n const head = /^\\s*chain\\s+\"?([^\"\\s{]+)\"?\\s*\\{/.exec(line);\n if (head !== null) inside = head[1]!.startsWith(\"service-\") && head[1]!.includes(ours);\n else if (/^\\s*(set|map)\\s/.test(line)) inside = false;\n if (!inside) continue;\n // one chain for each endpoint, as kube-proxy 1.31 writes it\n for (const match of line.matchAll(/(?:goto|jump)\\s+\"?endpoint-[0-9A-Za-z]+(-[^\\s,}\"]+)__([0-9.]+)\\/(\\d+)\"?/g))\n if (match[1]!.startsWith(ours)) out.add(`${match[2]}:${match[3]}`);\n // a DNAT in the service chain, as kube-proxy 1.37 writes it: `numgen random mod 2 map { 0 : IP . PORT, … }`, or `dnat to IP:PORT`\n for (const match of line.matchAll(/(?:^|[\\s{,:])(\\d+\\.\\d+\\.\\d+\\.\\d+) \\. (\\d+)(?=\\s*[,}]|\\s*$)/g)) out.add(`${match[1]}:${match[2]}`);\n for (const match of line.matchAll(/\\bdnat (?:ip )?to (\\d+\\.\\d+\\.\\d+\\.\\d+):(\\d+)/g)) out.add(`${match[1]}:${match[2]}`);\n }\n } else {\n let inside = false;\n for (const line of lines) {\n // each virtual service starts a block: a UDP or an SCTP one ends the block of the TCP service before it\n const service = /^(TCP|UDP|SCTP)\\s+([0-9A-F]{8}):([0-9A-F]{4})\\s/.exec(line);\n if (service !== null) {\n inside = service[1] === \"TCP\" && hexIp(service[2]!) === rule.ip && parseInt(service[3]!, 16) === rule.port;\n continue;\n }\n const real = /^\\s+->\\s+([0-9A-F]{8}):([0-9A-F]{4})\\s+\\S+\\s+(\\d+)/.exec(line);\n if (inside && real !== null && Number(real[3]) > 0) out.add(`${hexIp(real[1]!)}:${parseInt(real[2]!, 16)}`);\n }\n }\n return [...out].sort();\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\n\n/** A kube-proxy mode is the dataplane of each node: iptables (the default of kind), IPVS or nftables. */\nexport type KubeProxyMode = \"iptables\" | \"ipvs\" | \"nftables\";\nexport const KUBE_PROXY_MODES: readonly KubeProxyMode[] = [\"iptables\", \"ipvs\", \"nftables\"];\n\n/** A node image is the kind node image of one Kubernetes version, pinned by digest. */\nexport interface NodeImage {\n readonly kubernetes: string;\n readonly image: string;\n}\n\n/**\n * These are the node images of kind v0.33.0, from its release notes. The image of Kubernetes 1.37\n * is v1.37.0. The research names v1.37.1 for some defaults, thus a default that changed in the\n * patch release is a mismatch to record.\n */\nexport const NODE_IMAGES: Readonly<Record<string, NodeImage>> = {\n \"1.37\": {\n kubernetes: \"v1.37.0\",\n image: \"kindest/node:v1.37.0@sha256:a1ed56cfb0e7b93589bdf97c8cd566405a265939e3620fc4f5de89adff580ae5\",\n },\n \"1.36\": {\n kubernetes: \"v1.36.4\",\n image: \"kindest/node:v1.36.4@sha256:099e049362a1526b2db71494e1947aae99bd16290d7c895f2b7ea312e3cbfaed\",\n },\n};\n\n/**\n * A scenario is one kind cluster as data. The lab generates the configuration of kind from it, and\n * nothing comes from a default of kind. The scenario gives the node count, the version, the mode\n * of kube-proxy and its sync periods, the CNI and the subnets.\n */\nexport interface KindScenario {\n /** This name names the cluster and its Docker network: lowercase letters, digits and dashes. */\n readonly name: string;\n /** This key selects the node image (`NODE_IMAGES`). */\n readonly kubernetes: string;\n /** This count is the number of workers. The cluster also has one control plane. */\n readonly workers: number;\n readonly kubeProxy: {\n readonly mode: KubeProxyMode;\n /** This time is the shortest interval between two syncs of the rules. */\n readonly minSyncPeriod: Quantity;\n /** This time is the interval of the full sync of the rules. */\n readonly syncPeriod: Quantity;\n };\n /** This CNI is kindnet, the CNI of kind. Cilium comes later. */\n readonly cni: \"kindnet\";\n readonly podSubnet: string;\n readonly serviceSubnet: string;\n}\n\n/** This function gives a scenario with the defaults of Kubernetes for kube-proxy, as explicit values. */\nexport const scenario = (name: string, mode: KubeProxyMode, workers: number, kubernetes = \"1.37\"): KindScenario => ({\n name,\n kubernetes,\n workers,\n kubeProxy: { mode, minSyncPeriod: { value: 1, unit: \"s\" }, syncPeriod: { value: 30, unit: \"s\" } },\n cni: \"kindnet\",\n podSubnet: \"10.244.0.0/16\",\n serviceSubnet: \"10.96.0.0/16\",\n});\n\nconst NAME = /^[a-z0-9]([a-z0-9-]{0,30}[a-z0-9])?$/;\n\n/** This function gives the problems of a scenario: a name that kind refuses, no image for the version, or a count out of range. */\nexport const scenarioProblems = (s: KindScenario): string[] => {\n const problems: string[] = [];\n if (!NAME.test(s.name)) problems.push(`the name ${s.name} is not lowercase letters, digits and dashes of 1 to 32 characters`);\n if (NODE_IMAGES[s.kubernetes] === undefined) problems.push(`no node image is pinned for Kubernetes ${s.kubernetes}`);\n if (!Number.isInteger(s.workers) || s.workers < 0 || s.workers > 8) problems.push(`${s.workers} workers is not a whole number from 0 to 8`);\n if (cidrOverlap(s.podSubnet, s.serviceSubnet)) problems.push(`the pod subnet ${s.podSubnet} overlaps the service subnet ${s.serviceSubnet}`);\n return problems;\n};\n\n/** This function gives the names of the node containers, as kind names them: the control plane, then each worker. */\nexport const nodeNames = (s: KindScenario): string[] => [\n `${s.name}-control-plane`,\n ...Array.from({ length: s.workers }, (_, at) => `${s.name}-worker${at === 0 ? \"\" : at + 1}`),\n];\n\n/** This function gives a duration as kube-proxy reads it, in whole milliseconds: `1s`, `500ms`. */\nexport const goDuration = (amount: Quantity): string => {\n const ms = valueIn(amount, \"ms\");\n if (!Number.isInteger(ms) || ms < 0) throw new RangeError(`${ms} ms is not a whole number of milliseconds`);\n return ms % 1000 === 0 ? `${ms / 1000}s` : `${ms}ms`;\n};\n\n/** This function gives the KubeProxyConfiguration patch of a scenario, as kubeadm reads it. */\nexport const kubeProxyPatch = (s: KindScenario): string =>\n [\n \"apiVersion: kubeproxy.config.k8s.io/v1alpha1\",\n \"kind: KubeProxyConfiguration\",\n `mode: ${s.kubeProxy.mode}`,\n `${s.kubeProxy.mode}:`,\n ` minSyncPeriod: ${goDuration(s.kubeProxy.minSyncPeriod)}`,\n ` syncPeriod: ${goDuration(s.kubeProxy.syncPeriod)}`,\n \"\",\n ].join(\"\\n\");\n\n/** A kind configuration is the cluster file that `kind create cluster --config` reads. JSON is YAML, thus the lab writes it as JSON. */\nexport interface KindConfig {\n readonly kind: \"Cluster\";\n readonly apiVersion: \"kind.x-k8s.io/v1alpha4\";\n readonly networking: {\n readonly ipFamily: \"ipv4\";\n readonly kubeProxyMode: KubeProxyMode;\n readonly disableDefaultCNI: boolean;\n readonly podSubnet: string;\n readonly serviceSubnet: string;\n };\n readonly nodes: ReadonlyArray<{\n readonly role: \"control-plane\" | \"worker\";\n readonly image: string;\n readonly labels: Readonly<Record<string, string>>;\n }>;\n readonly kubeadmConfigPatches: readonly string[];\n}\n\n/** This function generates the configuration of kind from a scenario. It refuses a scenario with a problem. */\nexport const kindConfig = (s: KindScenario): KindConfig => {\n const problems = scenarioProblems(s);\n if (problems.length > 0) throw new Error(`the scenario ${s.name}: ${problems.join(\"; \")}`);\n const image = NODE_IMAGES[s.kubernetes]!.image;\n return {\n kind: \"Cluster\",\n apiVersion: \"kind.x-k8s.io/v1alpha4\",\n networking: {\n ipFamily: \"ipv4\",\n kubeProxyMode: s.kubeProxy.mode,\n disableDefaultCNI: false,\n podSubnet: s.podSubnet,\n serviceSubnet: s.serviceSubnet,\n },\n nodes: nodeNames(s).map((node, at) => ({\n role: at === 0 ? \"control-plane\" : \"worker\",\n image,\n labels: { \"lab.systems/node\": node.slice(s.name.length + 1) },\n })),\n kubeadmConfigPatches: [kubeProxyPatch(s)],\n };\n};\n\n/** This function gives the first and the last address of an IPv4 CIDR, as numbers. */\nexport const cidrRange = (cidr: string): [number, number] => {\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\/(\\d+)$/.exec(cidr);\n if (match === null) throw new Error(`not an IPv4 CIDR: ${cidr}`);\n const octets = match.slice(1, 5).map(Number);\n const bits = Number(match[5]);\n if (octets.some((octet) => octet > 255) || bits > 32) throw new Error(`not an IPv4 CIDR: ${cidr}`);\n const base = octets.reduce((sum, octet) => sum * 256 + octet, 0);\n const size = 2 ** (32 - bits);\n const first = Math.floor(base / size) * size;\n return [first, first + size - 1];\n};\n\n/** This function tells if two IPv4 CIDRs share an address. */\nexport const cidrOverlap = (a: string, b: string): boolean => {\n const [a0, a1] = cidrRange(a);\n const [b0, b1] = cidrRange(b);\n return a0 <= b1 && b0 <= a1;\n};\n","import { createHash } from \"node:crypto\";\nimport { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { gunzipSync } from \"node:zlib\";\n\n/**\n * A tool is a binary that the lab downloads, at one pinned release, for each platform. The lab\n * keeps it in the cache folder of this package and does not install it on the system. Each use\n * compares its sha256 with the pinned value.\n *\n * A tool with an `entry` comes in a gzipped tar file. The pinned sha256 is the sha256 of that file.\n * The cache keeps the file, and each use takes the entry from it again.\n */\nexport interface Tool {\n readonly name: \"kind\" | \"kubectl\" | \"istioctl\";\n readonly version: string;\n /** These are the release assets, by the platform and the architecture of Node: `win32-x64`, `linux-x64`. */\n readonly assets: Readonly<Record<string, { readonly url: string; readonly sha256: string }>>;\n /** This name is the file of the tool in its gzipped tar file, or nothing for a tool that is its own asset. */\n readonly entry?: string | undefined;\n}\n\n/** This release of kind is v0.33.0, from 2026-08-26. Its default node image is Kubernetes 1.37.0. The sums are from its `.sha256sum` assets. */\nexport const KIND: Tool = {\n name: \"kind\",\n version: \"v0.33.0\",\n assets: {\n \"win32-x64\": {\n url: \"https://github.com/kubernetes-sigs/kind/releases/download/v0.33.0/kind-windows-amd64\",\n sha256: \"4b22adaa135368c5a465d56bbd8e520cbea87272a06ca00b6078e7b81515c9fc\",\n },\n \"linux-x64\": {\n url: \"https://github.com/kubernetes-sigs/kind/releases/download/v0.33.0/kind-linux-amd64\",\n sha256: \"aee6151561422756b764a4ae28e7f44cda5af5a9eead3cc9985112b1de8d8e0d\",\n },\n \"linux-arm64\": {\n url: \"https://github.com/kubernetes-sigs/kind/releases/download/v0.33.0/kind-linux-arm64\",\n sha256: \"20022bee6cfcd5086cb7234d218e3454e6090022f2a8f55d1fa7fcf42c3867a2\",\n },\n \"darwin-arm64\": {\n url: \"https://github.com/kubernetes-sigs/kind/releases/download/v0.33.0/kind-darwin-arm64\",\n sha256: \"0c8c7dbe5e23594a198b786c4bc13dacc101fa6196b0cb0b23a1ca44e61f4b4f\",\n },\n },\n};\n\n/** This release of kubectl is v1.37.0, the version of the node image. The sums are from the `.sha256` files of `dl.k8s.io`. */\nexport const KUBECTL: Tool = {\n name: \"kubectl\",\n version: \"v1.37.0\",\n assets: {\n \"win32-x64\": {\n url: \"https://dl.k8s.io/release/v1.37.0/bin/windows/amd64/kubectl.exe\",\n sha256: \"4721b614a67bb4932a0369e61f4a323d8c6ca00943d3a2ff14837c124da06f0e\",\n },\n \"linux-x64\": {\n url: \"https://dl.k8s.io/release/v1.37.0/bin/linux/amd64/kubectl\",\n sha256: \"6129359f4e1f3848a5572ccb0b26cf28b8ca08cef38c95a765b2f64a2c961a2f\",\n },\n \"linux-arm64\": {\n url: \"https://dl.k8s.io/release/v1.37.0/bin/linux/arm64/kubectl\",\n sha256: \"922df28df248cc00a9e025f947704f1d1482de64ece54cfe57e61f19eaf1eef3\",\n },\n \"darwin-arm64\": {\n url: \"https://dl.k8s.io/release/v1.37.0/bin/darwin/arm64/kubectl\",\n sha256: \"583beedaebe422e71d3f1a96acef8b1fef86ea2f09a45ad01aa6c9ce287c1380\",\n },\n },\n};\n\n/**\n * This release of istioctl is 1.31.3, from 2026-10-22. It holds the charts of its own version, thus the\n * lab needs no other tool to install Istio. The sums are from the `.sha256` assets of the release. The\n * release has a zip file for Windows, and the lab reads only a gzipped tar file. Thus Windows has no asset.\n */\nexport const ISTIOCTL: Tool = {\n name: \"istioctl\",\n version: \"1.31.3\",\n entry: \"istioctl\",\n assets: {\n \"linux-x64\": {\n url: \"https://github.com/istio/istio/releases/download/1.31.3/istioctl-1.31.3-linux-amd64.tar.gz\",\n sha256: \"382c3004ce2e9c5ed3064b092ae0da7f6bbe30c766a1be282f45725add004860\",\n },\n \"linux-arm64\": {\n url: \"https://github.com/istio/istio/releases/download/1.31.3/istioctl-1.31.3-linux-arm64.tar.gz\",\n sha256: \"7476801623dbb900073cb1dba6a23b6a15c564b7e92b208aed36829315fefff1\",\n },\n \"darwin-arm64\": {\n url: \"https://github.com/istio/istio/releases/download/1.31.3/istioctl-1.31.3-osx-arm64.tar.gz\",\n sha256: \"881c3b443d9e458e8f19feed6ffa187dcfcb03dc032cb56068f7d1d0cd976c82\",\n },\n },\n};\n\n/**\n * This function gives the bytes of one file of a gzipped tar file. A header of the ustar format has\n * 512 bytes. It has the name at 0, the size in octal at 124, the type at 156 and the prefix of the\n * name at 345. The data follows the header, in blocks of 512 bytes. The match is on the last part of the\n * name. A link, a folder and an extended header are not a file, thus the search skips them.\n */\nexport const tarEntry = (gzipped: Uint8Array, entry: string): Uint8Array => {\n const tar = gunzipSync(gzipped);\n const text = (from: number, length: number): string => {\n const field = tar.subarray(from, from + length);\n const end = field.indexOf(0);\n return field.subarray(0, end === -1 ? field.length : end).toString(\"latin1\");\n };\n let at = 0;\n while (at + 512 <= tar.length) {\n const name = text(at, 100);\n if (name === \"\") break;\n const size = Number.parseInt(text(at + 124, 12).trim() || \"0\", 8);\n if (!Number.isFinite(size)) throw new Error(`the tar entry ${name} has no size in octal`);\n const type = String.fromCharCode(tar[at + 156] ?? 0);\n const prefix = text(at + 345, 155);\n const full = prefix === \"\" ? name : `${prefix}/${name}`;\n if ((type === \"0\" || type === \"\\0\") && full.split(\"/\").at(-1) === entry) return new Uint8Array(tar.subarray(at + 512, at + 512 + size));\n at += 512 + Math.ceil(size / 512) * 512;\n }\n throw new Error(`the tar file has no file ${entry}`);\n};\n\n/** This folder is the cache of the tools: `.cache/` in this package, which git ignores. `KIND_CACHE` names another folder. */\nexport const cacheFolder = (): string =>\n process.env[\"KIND_CACHE\"] ?? fileURLToPath(new URL(\"../.cache/\", import.meta.url));\n\n/** This function gives the platform key of this process, as the assets name it. */\nexport const platformKey = (platform: string = process.platform, arch: string = process.arch): string =>\n `${platform}-${arch}`;\n\n/** This function gives the sha256 of some bytes, in hexadecimal. */\nexport const sha256 = (bytes: Uint8Array): string => createHash(\"sha256\").update(bytes).digest(\"hex\");\n\n/** This function gives the path of a tool in the cache, for a platform. */\nexport const toolPath = (tool: Tool, key: string = platformKey(), cache: string = cacheFolder()): string =>\n join(cache, \"tools\", `${tool.name}-${tool.version}-${key}`, key.startsWith(\"win32\") ? `${tool.name}.exe` : tool.name);\n\n/** This function writes a file through a partial file, thus a stop in the middle leaves no file at the path. */\nconst writeWhole = (path: string, bytes: Uint8Array, executable: boolean): void => {\n mkdirSync(dirname(path), { recursive: true });\n const partial = `${path}.partial`;\n writeFileSync(partial, bytes);\n if (executable) chmodSync(partial, 0o755);\n renameSync(partial, path);\n};\n\n/** These are the waits in ms before the second and the third try of a download. A runner can fail to connect for a moment. */\nexport const DOWNLOAD_WAITS: readonly number[] = [2_000, 8_000];\n\n/**\n * This function gives the bytes of a URL. It tries again after each wait when the connection fails or\n * the answer is a 5xx. It refuses each other answer that is not ok at once.\n */\nexport const download = async (url: string, waits: readonly number[] = DOWNLOAD_WAITS): Promise<Uint8Array> => {\n for (let attempt = 0; ; attempt++) {\n const response = await fetch(url, { redirect: \"follow\" }).catch((error: unknown) => (error instanceof Error ? error : new Error(String(error))));\n if (response instanceof Response && response.ok) return new Uint8Array(await response.arrayBuffer());\n const failure = response instanceof Response ? new Error(`the download of ${url} answered ${response.status}`) : response;\n const wait = waits[attempt];\n if ((response instanceof Response && response.status < 500) || wait === undefined) throw failure;\n await new Promise((resolve) => setTimeout(resolve, wait));\n }\n};\n\n/**\n * This function gives the path of a tool, after a download when the cache does not have it. It\n * refuses bytes whose sha256 is not the pinned value, from the cache or from the download. For a\n * tool in a tar file, the cache keeps the tar file beside the tool. Each use examines the tar file,\n * and writes the tool again when its bytes are not those of the entry.\n */\nexport const ensureTool = async (tool: Tool, key: string = platformKey(), cache: string = cacheFolder()): Promise<string> => {\n const asset = tool.assets[key];\n if (asset === undefined) throw new Error(`${tool.name} ${tool.version} has no pinned asset for ${key}`);\n const path = toolPath(tool, key, cache);\n const executable = !key.startsWith(\"win32\");\n // the downloaded asset: the tool itself, or its tar file\n const kept = tool.entry === undefined ? path : `${path}.tar.gz`;\n let bytes: Uint8Array;\n if (existsSync(kept)) {\n bytes = readFileSync(kept);\n const found = sha256(bytes);\n if (found !== asset.sha256) throw new Error(`${kept} has the sha256 ${found}, not the pinned ${asset.sha256}`);\n if (tool.entry === undefined) return path;\n } else {\n bytes = await download(asset.url);\n const found = sha256(bytes);\n if (found !== asset.sha256) throw new Error(`${asset.url} has the sha256 ${found}, not the pinned ${asset.sha256}`);\n writeWhole(kept, bytes, executable && tool.entry === undefined);\n if (tool.entry === undefined) return path;\n }\n const binary = tarEntry(bytes, tool.entry);\n if (!existsSync(path) || sha256(readFileSync(path)) !== sha256(binary)) writeWhole(path, binary, executable);\n return path;\n};\n","import { execFile } from \"node:child_process\";\nimport { mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport type { ClusterManifest } from \"./record.ts\";\nimport type { KindScenario } from \"./scenario.ts\";\nimport { NODE_IMAGES, cidrOverlap, kindConfig, nodeNames } from \"./scenario.ts\";\nimport { KIND, KUBECTL, ensureTool } from \"./tools.ts\";\n\nconst run = promisify(execFile);\n\n/** This function starts a program and gives its standard output. A failure gives its standard error in the message. */\nexport const command = async (\n program: string,\n args: readonly string[],\n options: { readonly env?: Readonly<Record<string, string>>; readonly input?: string; readonly timeout?: number } = {},\n): Promise<string> => {\n try {\n const child = run(program, [...args], {\n env: { ...process.env, ...options.env },\n maxBuffer: 256 << 20,\n timeout: options.timeout ?? 600_000,\n windowsHide: true,\n });\n if (options.input !== undefined) {\n child.child.stdin?.end(options.input);\n }\n const { stdout } = await child;\n return stdout.toString();\n } catch (error) {\n const failed = error as { stderr?: string; stdout?: string; message?: string };\n throw new Error(`${program} ${args.join(\" \")} failed: ${(failed.stderr ?? failed.message ?? \"\").toString().trim().slice(0, 2000)}`);\n }\n};\n\n/** The credentials of the API, as a container on the network of the cluster reaches it. */\nexport interface Credentials {\n readonly server: string;\n readonly ca: string;\n readonly cert: string;\n readonly key: string;\n}\n\n/** A cluster is one kind cluster that the lab made, with its own kubeconfig and Docker network. */\nexport interface Cluster {\n readonly scenario: KindScenario;\n readonly dir: string;\n readonly kubeconfig: string;\n readonly network: string;\n readonly nodes: readonly string[];\n kubectl(args: readonly string[], input?: string): Promise<string>;\n /** This method applies Kubernetes objects, as JSON on standard input. */\n apply(objects: readonly unknown[]): Promise<void>;\n /** This method starts a command in a node container. */\n exec(node: string, argv: readonly string[]): Promise<string>;\n /** This method copies a local Docker image into each node. */\n load(image: string): Promise<void>;\n credentials(): Promise<Credentials>;\n manifest(): Promise<ClusterManifest>;\n delete(): Promise<void>;\n}\n\n/** This function gives the subnets of the Docker networks, by their names. */\nconst dockerSubnets = async (): Promise<Array<[string, string]>> => {\n const ids = (await command(\"docker\", [\"network\", \"ls\", \"-q\"])).split(/\\s+/).filter((id) => id !== \"\");\n if (ids.length === 0) return [];\n const info = JSON.parse(await command(\"docker\", [\"network\", \"inspect\", ...ids])) as Array<{\n Name: string;\n IPAM?: { Config?: Array<{ Subnet?: string }> | null };\n }>;\n return info.flatMap((network) =>\n (network.IPAM?.Config ?? [])\n .map((config) => config.Subnet ?? \"\")\n .filter((subnet) => /^\\d+\\.\\d+\\.\\d+\\.\\d+\\/\\d+$/.test(subnet))\n .map((subnet): [string, string] => [network.Name, subnet]),\n );\n};\n\n/**\n * This function makes a kind cluster from a scenario, in a folder that holds its config, its\n * kubeconfig and the cache of kubectl. The cluster gets a Docker network of its own. The function\n * refuses a pod or service subnet that overlaps a Docker network, because that breaks the traffic\n * between the nodes.\n */\nexport const createCluster = async (scenario: KindScenario, dir: string): Promise<Cluster> => {\n const kind = await ensureTool(KIND);\n const kubectlPath = await ensureTool(KUBECTL);\n mkdirSync(dir, { recursive: true });\n const kubeconfig = join(dir, \"kubeconfig\");\n const network = `systems-${scenario.name}`;\n const config = kindConfig(scenario);\n for (const [name, subnet] of await dockerSubnets()) {\n if (name === network) continue;\n for (const ours of [scenario.podSubnet, scenario.serviceSubnet])\n if (cidrOverlap(ours, subnet)) throw new Error(`the subnet ${ours} overlaps the Docker network ${name} (${subnet})`);\n }\n const file = join(dir, \"kind.json\");\n writeFileSync(file, `${JSON.stringify(config, null, 2)}\\n`);\n const env = { KIND_EXPERIMENTAL_DOCKER_NETWORK: network };\n const nodes = nodeNames(scenario);\n const kubectl = (args: readonly string[], input?: string) =>\n command(kubectlPath, [\"--kubeconfig\", kubeconfig, \"--cache-dir\", join(dir, \"kubectl-cache\"), ...args], input === undefined ? {} : { input });\n const cluster: Cluster = {\n scenario,\n dir,\n kubeconfig,\n network,\n nodes,\n kubectl,\n async apply(objects) {\n await kubectl([\"apply\", \"-f\", \"-\"], JSON.stringify({ apiVersion: \"v1\", kind: \"List\", items: objects }));\n },\n exec: (node, argv) => command(\"docker\", [\"exec\", node, ...argv]),\n async load(image) {\n await command(kind, [\"load\", \"docker-image\", \"--name\", scenario.name, image], { env, timeout: 900_000 });\n },\n async credentials() {\n const internal = join(dir, \"kubeconfig.internal\");\n writeFileSync(internal, await command(kind, [\"get\", \"kubeconfig\", \"--internal\", \"--name\", scenario.name], { env }));\n const view = JSON.parse(\n await command(kubectlPath, [\"--kubeconfig\", internal, \"config\", \"view\", \"--raw\", \"-o\", \"json\"]),\n ) as {\n clusters: Array<{ cluster: { server: string; \"certificate-authority-data\": string } }>;\n users: Array<{ user: { \"client-certificate-data\": string; \"client-key-data\": string } }>;\n };\n return {\n server: view.clusters[0]!.cluster.server,\n ca: view.clusters[0]!.cluster[\"certificate-authority-data\"],\n cert: view.users[0]!.user[\"client-certificate-data\"],\n key: view.users[0]!.user[\"client-key-data\"],\n };\n },\n async manifest() {\n const nodesJson = JSON.parse(await kubectl([\"get\", \"nodes\", \"-o\", \"json\"])) as {\n items: Array<{ metadata: { name: string }; status: { nodeInfo: Record<string, string> } }>;\n };\n const proxy = JSON.parse(await kubectl([\"-n\", \"kube-system\", \"get\", \"configmap\", \"kube-proxy\", \"-o\", \"json\"])) as {\n data: Record<string, string>;\n };\n const mode = /^mode:\\s*(\\S*)/m.exec(proxy.data[\"config.conf\"] ?? \"\")?.[1] ?? \"\";\n const version = JSON.parse(await kubectl([\"version\", \"-o\", \"json\"])) as { serverVersion?: { gitVersion?: string } };\n return {\n kind: (await command(kind, [\"version\"])).trim(),\n nodeImage: NODE_IMAGES[scenario.kubernetes]!.image,\n server: version.serverVersion?.gitVersion ?? \"\",\n mode: mode.replaceAll('\"', \"\"),\n docker: (await command(\"docker\", [\"version\", \"--format\", \"{{.Server.Version}}\"])).trim(),\n nodes: nodesJson.items.map((item) => ({\n name: item.metadata.name,\n kubelet: item.status.nodeInfo[\"kubeletVersion\"] ?? \"\",\n kernel: item.status.nodeInfo[\"kernelVersion\"] ?? \"\",\n runtime: item.status.nodeInfo[\"containerRuntimeVersion\"] ?? \"\",\n os: item.status.nodeInfo[\"osImage\"] ?? \"\",\n })),\n };\n },\n async delete() {\n await command(kind, [\"delete\", \"cluster\", \"--name\", scenario.name, \"--kubeconfig\", kubeconfig], { env });\n await command(\"docker\", [\"network\", \"rm\", network]).catch(() => undefined);\n },\n };\n await command(\n kind,\n [\"create\", \"cluster\", \"--name\", scenario.name, \"--config\", file, \"--kubeconfig\", kubeconfig, \"--wait\", \"180s\"],\n { env, timeout: 900_000 },\n );\n return cluster;\n};\n","// The composition of the service of a kind cell. The app of the image and `lint()` compose the same\n// service from the same declaration: the image copies this file, and Node strips its types. Thus\n// this file imports only packages and types.\nimport type { CheckFn, HealthServer, ServiceBuilder, TransportImpl } from \"@mark1russell7/service\";\nimport { service } from \"@mark1russell7/service\";\nimport type { RedisClient } from \"@mark1russell7/service/redis\";\nimport type { Declaration } from \"./cell.ts\";\n\n/** These are the parts of a running service. A composition for `lint()` gives none of them. */\nexport interface Parts {\n readonly redis?: RedisClient | undefined;\n readonly flag?: CheckFn | undefined;\n readonly http?: TransportImpl | undefined;\n readonly health?: HealthServer | undefined;\n}\n\n/** This name is the controller of the service, and its one route reads Redis when the service has Redis. */\nexport const CONTROLLER = \"work\";\nexport const ROUTE = \"GET /work\";\n/** This name is the check that a flag file fails: the seam of the liveness restart. */\nexport const FLAG_CHECK = \"self.flag\";\n/** This name is the watchdog of the event loop. */\nexport const WATCHDOG = \"self.event-loop\";\n\n/**\n * This function composes the service of a declaration: its policies, its couplings, its checks,\n * one server, one controller and each stage of the shutdown. A declaration with Redis needs the\n * client, because the client declares the dependency and its checks.\n */\nexport const compose = (name: string, declaration: Declaration, parts: Parts = {}): ServiceBuilder => {\n const svc = service(name)\n .readiness(declaration.readiness)\n .liveness(declaration.liveness)\n .startup(declaration.startup)\n .cooperativeWindow(declaration.cooperativeWindow)\n // the kubelet sends SIGTERM one time and SIGKILL at the end of the grace: a repeat signal has no effect\n .onRepeatSignal(\"ignore\");\n for (const coupling of declaration.couplings) svc.couple(coupling);\n if (declaration.redis !== null) {\n if (parts.redis === undefined) throw new Error(`${name} declares Redis, thus its composition needs the client`);\n parts.redis.attach(svc, { poll: declaration.poll, steps: { \"round-trip\": declaration.redis.gates } });\n }\n if (declaration.flag !== null)\n svc.check(\n {\n name: FLAG_CHECK,\n dependency: null,\n step: \"local-state\",\n ownConnection: true,\n gates: declaration.flag,\n poll: declaration.poll,\n },\n parts.flag,\n );\n if (declaration.watchdog !== null)\n svc.watchdog({\n name: WATCHDOG,\n tick: declaration.watchdog.tick,\n threshold: declaration.watchdog.threshold,\n gates: \"liveness\",\n poll: declaration.poll,\n });\n svc.server(\"http\", {}, parts.http);\n const controller = svc.controller(CONTROLLER);\n if (declaration.redis !== null) controller.requires(parts.redis!.name);\n controller.route(ROUTE, { idempotent: true });\n if (parts.health !== undefined) svc.healthServer(parts.health);\n for (const [stage, cap] of declaration.phases) svc.phase(stage, cap);\n return svc;\n};\n","// The hosts of one cluster in the Envoy of a sidecar, from the text of its admin endpoint\n// `/clusters`. The agent of the lab image reads it with Node, thus this module imports nothing.\n\n/**\n * These are the hosts of a cluster: the healthy ones (`b`, in sequence), and each other host with its\n * health flags (`u`). Each host is `ip:port`.\n */\nexport interface ClusterHosts {\n readonly b: readonly string[];\n readonly u: Readonly<Record<string, string>>;\n}\n\n/**\n * This function gives the name of the outbound cluster of a Service port in a sidecar of Istio:\n * `outbound|<port>||<service>.<namespace>.svc.cluster.local`.\n */\nexport const outboundCluster = (service: string, namespace: string, port: number): string => `outbound|${port}||${service}.${namespace}.svc.cluster.local`;\n\n/** This function gives the filter of `/clusters` that selects one cluster: a regular expression of RE2 with anchors. */\nexport const clusterFilter = (cluster: string): string => `^${cluster.replace(/[.|\\\\^$*+?()[\\]{}]/g, (c) => `\\\\${c}`)}$`;\n\n/**\n * This function gives the hosts of a cluster from the text of `/clusters`. Each host of the text has\n * a line `<cluster>::<ip:port>::health_flags::<flags>`. The flags are `healthy`, or the names of the\n * flags that the host has (`clusters_handler.cc` and `host_utility.cc` of Envoy v1.39.4).\n */\nexport const hostsOf = (text: string, cluster: string): ClusterHosts => {\n const healthy: string[] = [];\n const other: Record<string, string> = {};\n const prefix = `${cluster}::`;\n for (const line of text.split(\"\\n\")) {\n if (!line.startsWith(prefix)) continue;\n const parts = line.slice(prefix.length).split(\"::\");\n if (parts.length !== 3 || parts[1] !== \"health_flags\") continue;\n const [host, , flags] = parts as [string, string, string];\n if (flags.trim() === \"healthy\") healthy.push(host);\n else other[host] = flags.trim();\n }\n return { b: healthy.sort(), u: other };\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { KindCell } from \"./cell.ts\";\nimport { CLIENT, REDIS, SERVER } from \"./names.ts\";\n\n/**\n * This module maps each trigger of a cell to the acts of the lab, as data. The runner finds each\n * target at the time of the run. A target is a pod, the PID of a container, the network namespace\n * of a pod, or a cgroup.\n */\n\n/**\n * A target is a process or a pod of the cell:\n * - `server` is the first replica of the service, in the sequence of its name.\n * - `redis` is the Redis pod.\n * - `kube-proxy` is kube-proxy on the first worker.\n * - `servers` is each replica of the service. Only the injector takes it: a rule in the network\n * namespace of each replica, with each client pod as a peer. A REJECT resets only the sender of\n * the segment that it matches, thus the rule goes where the segments of the clients arrive.\n * - `clients` is the process of each client pod. Only a signal takes it: the gap of the trigger `idle`.\n */\nexport type Target = \"server\" | \"redis\" | \"kube-proxy\" | \"servers\" | \"clients\";\n\n/** A step is one act of a fault, in the sequence of the plan. */\nexport type Step =\n | { readonly do: \"kubectl\"; readonly args: readonly string[]; readonly background?: boolean }\n /**\n * This step sends a signal to the target. For the target `clients`, it sends it to each client\n * process, the nodes at the same time. SIGUSR1 stops the calls of a gRPC client, and SIGUSR2 starts them again.\n */\n | { readonly do: \"signal\"; readonly target: Target; readonly signal: \"SIGKILL\" | \"SIGSTOP\" | \"SIGCONT\" | \"SIGUSR1\" | \"SIGUSR2\" }\n /** This step sends SIGSTOP, then SIGCONT `time` later, as the VM times it. The next step starts at once. */\n | { readonly do: \"pause\"; readonly target: Target; readonly time: Quantity }\n /**\n * This step starts the injector of the harness in the namespace of the target, with each replica of\n * the service as a peer. For the target `servers`, it starts it in the namespace of each replica,\n * with each client pod as a peer. The rules of all replicas commit at one instant.\n */\n | { readonly do: \"inject\"; readonly target: Target; readonly action: \"blackhole\" | \"clear\" | \"rst\" | \"clear-rst\" }\n | { readonly do: \"flag\"; readonly target: Target; readonly on: boolean }\n | { readonly do: \"cpu\"; readonly target: Target; readonly max: string }\n /** This step blocks the event loop of the target for `time`. */\n | { readonly do: \"stall\"; readonly target: Target; readonly time: Quantity }\n | { readonly do: \"wait\"; readonly time: Quantity }\n /** This step applies one object with `kubectl apply`. The runner fills the marks of its strings, for example `{namespace}`. */\n | { readonly do: \"apply\"; readonly object: Readonly<Record<string, unknown>> };\n\n/**\n * A fault plan is the steps of a run:\n * - `inject` starts the trigger. The stamp of its first act is the `fault` reference.\n * - `heal` ends the fault, `hold` after the fault. A trigger that ends by itself has no heal.\n * - `restore` brings the cell back to its steady state after the run.\n */\nexport interface FaultPlan {\n readonly inject: readonly Step[];\n readonly hold: Quantity | null;\n readonly heal: readonly Step[];\n readonly restore: readonly Step[];\n}\n\nconst needs = (time: Quantity | undefined, what: string): Quantity => {\n if (time === undefined) throw new Error(`the trigger needs ${what}`);\n return time;\n};\n\n/** This function gives an amount in microseconds as a whole number, for `cpu.max`. */\nconst micros = (amount: Quantity): number => Math.round(valueIn(amount, \"us\"));\n\n/** This value marks the name of the target pod in the arguments of kubectl. The runner replaces it. */\nexport const POD = \"{pod}\";\n/** This value marks the node of the target pod in the arguments of kubectl. */\nexport const NODE = \"{node}\";\n/** This value marks the next version of the Deployment, for a rollout. */\nexport const VERSION = \"{version}\";\n/** This annotation of a pod is its deletion cost for its ReplicaSet: a lower cost goes first at a scale-in. */\nexport const DELETION_COST = \"controller.kubernetes.io/pod-deletion-cost\";\n/** This name is the name of the AuthorizationPolicy of the trigger `authorization-policy`. */\nexport const POLICY = \"lab-deny\";\n/** This service account is the account of the clients: their pods name no other account. */\nexport const CLIENT_ACCOUNT = \"default\";\n/** This selector gives the ztunnel pods in istio-system: the label `app` of the chart of ztunnel. */\nexport const ZTUNNEL_PODS = \"app=ztunnel\";\n\n/**\n * This function gives the AuthorizationPolicy of the trigger `authorization-policy`. It denies each\n * request to the server pods from the principal of the service account `account`. Istio gives a\n * pod of the mesh the principal `cluster.local/ns/<namespace>/sa/<account>` of its account. The\n * RBAC filter of the server sidecar reads the principal of the peer from its mTLS certificate.\n */\nexport const policyObject = (account: string): Readonly<Record<string, unknown>> => ({\n apiVersion: \"security.istio.io/v1\",\n kind: \"AuthorizationPolicy\",\n metadata: { name: POLICY, namespace: \"{namespace}\" },\n spec: {\n selector: { matchLabels: { app: SERVER } },\n action: \"DENY\",\n rules: [{ from: [{ source: { principals: [`cluster.local/ns/{namespace}/sa/${account}`] } }] }],\n },\n});\n\n/**\n * This function gives the target of a reset or a black hole of a cell. In T07 and T18 it is Redis,\n * with the replicas as peers. In T15 the fault is on the mesh leg between the client pods and the\n * replicas. It is a rule in each replica, with the client pods as peers.\n */\nconst linkOf = (cell: KindCell): Target => (cell.template === \"T15\" ? \"servers\" : \"redis\");\n\n/** This function gives the fault plan of a cell. */\nexport const planOf = (cell: KindCell): FaultPlan => {\n const ns = [\"-n\", \"{namespace}\"];\n const trigger = cell.trigger;\n const replicas = cell.pod.replicas;\n switch (trigger.kind) {\n case \"pod-deletion\":\n return { inject: [{ do: \"kubectl\", args: [...ns, \"delete\", \"pod\", POD, \"--wait=false\"] }], hold: null, heal: [], restore: [] };\n case \"rolling-update\":\n return {\n inject: [{ do: \"kubectl\", args: [...ns, \"set\", \"env\", `deployment/${SERVER}`, `LAB_VERSION=${VERSION}`] }],\n hold: null,\n heal: [],\n restore: [],\n };\n case \"node-drain\":\n return {\n inject: [\n {\n do: \"kubectl\",\n args: [\"drain\", NODE, \"--ignore-daemonsets\", \"--delete-emptydir-data\", \"--timeout=180s\", `--pod-selector=app=${SERVER}`],\n background: true,\n },\n ],\n hold: null,\n heal: [],\n restore: [{ do: \"kubectl\", args: [\"uncordon\", NODE] }],\n };\n case \"scale-in\":\n return {\n inject: [\n // the ReplicaSet removes a pod of a lower deletion cost first. The rank by readiness comes before\n // it, and the pods of a node come after it (pkg/controller/controller_utils.go at v1.37.0, lines\n // 853–874). The gate PodDeletionCost is on from 1.22 (pkg/features/kube_features.go, line 1953).\n // Thus the clients of gRPC on the first replica see its stop\n ...(cell.stack === \"grpc\" ? [{ do: \"kubectl\" as const, args: [...ns, \"annotate\", \"pod\", POD, `${DELETION_COST}=-1`, \"--overwrite\"] }] : []),\n { do: \"kubectl\", args: [...ns, \"scale\", `deployment/${SERVER}`, `--replicas=${replicas - 1}`] },\n ],\n hold: null,\n heal: [],\n restore: [{ do: \"kubectl\", args: [...ns, \"scale\", `deployment/${SERVER}`, `--replicas=${replicas}`] }],\n };\n case \"crash\":\n return { inject: [{ do: \"signal\", target: \"server\", signal: \"SIGKILL\" }], hold: null, heal: [], restore: [] };\n case \"liveness-restart\":\n // the container that the kubelet starts again has a new /tmp, thus the flag ends with it\n return { inject: [{ do: \"flag\", target: \"server\", on: true }], hold: null, heal: [], restore: [] };\n case \"freeze-process\":\n if (cell.template === \"T07\") {\n return {\n inject: [\n { do: \"pause\", target: \"kube-proxy\", time: needs(trigger.hold, \"a hold\") },\n { do: \"wait\", time: needs(trigger.lead, \"a lead\") },\n { do: \"kubectl\", args: [...ns, \"delete\", \"pod\", POD, \"--wait=false\"] },\n ],\n hold: null,\n heal: [],\n restore: [],\n };\n }\n return {\n inject: [{ do: \"signal\", target: \"redis\", signal: \"SIGSTOP\" }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"signal\", target: \"redis\", signal: \"SIGCONT\" }],\n restore: [],\n };\n case \"rst\":\n return {\n inject: [{ do: \"inject\", target: linkOf(cell), action: \"rst\" }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"inject\", target: linkOf(cell), action: \"clear-rst\" }],\n restore: [],\n };\n case \"blackhole\":\n return {\n inject: [{ do: \"inject\", target: linkOf(cell), action: \"blackhole\" }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"inject\", target: linkOf(cell), action: \"clear\" }],\n restore: [],\n };\n case \"peer-gone-without-close\":\n // the rules drop the FIN of Redis, then Redis goes away with its namespace and its rules\n return {\n inject: [\n { do: \"inject\", target: \"redis\", action: \"blackhole\" },\n { do: \"kubectl\", args: [...ns, \"scale\", `deployment/${REDIS}`, \"--replicas=0\"] },\n ],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"kubectl\", args: [...ns, \"scale\", `deployment/${REDIS}`, \"--replicas=1\"] }],\n restore: [],\n };\n case \"app-stall\":\n return { inject: [{ do: \"stall\", target: \"server\", time: needs(trigger.hold, \"a hold\") }], hold: null, heal: [], restore: [] };\n case \"rollout\":\n // Redis is down for each pod, a new one too: the Service of Redis has no endpoint\n return {\n inject: [\n { do: \"kubectl\", args: [...ns, \"scale\", `deployment/${REDIS}`, \"--replicas=0\"] },\n { do: \"kubectl\", args: [...ns, \"set\", \"env\", `deployment/${SERVER}`, `LAB_VERSION=${VERSION}`] },\n ],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"kubectl\", args: [...ns, \"scale\", `deployment/${REDIS}`, \"--replicas=1\"] }],\n restore: [],\n };\n case \"cpu-throttle\": {\n const max = trigger.cpuMax;\n if (max === undefined) throw new Error(\"the trigger needs its cpu.max\");\n // the kernel reads cpu.max as the quota and the period in microseconds\n return {\n inject: [{ do: \"cpu\", target: \"server\", max: `${micros(max.quota)} ${micros(max.period)}` }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"cpu\", target: \"server\", max: `max ${micros(max.period)}` }],\n restore: [],\n };\n }\n case \"authorization-policy\":\n // the restore deletes a policy that a failed heal left\n return {\n inject: [{ do: \"apply\", object: policyObject(trigger.account ?? CLIENT_ACCOUNT) }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"kubectl\", args: [...ns, \"delete\", \"authorizationpolicy\", POLICY] }],\n restore: [{ do: \"kubectl\", args: [...ns, \"delete\", \"authorizationpolicy\", POLICY, \"--ignore-not-found\"] }],\n };\n case \"idle\":\n // the gap of the clients: the restore starts the calls of a client that a failed heal left stopped\n return {\n inject: [{ do: \"signal\", target: \"clients\", signal: \"SIGUSR1\" }],\n hold: needs(trigger.hold, \"a hold, the time of the gap\"),\n heal: [{ do: \"signal\", target: \"clients\", signal: \"SIGUSR2\" }],\n restore: [{ do: \"signal\", target: \"clients\", signal: \"SIGUSR2\" }],\n };\n case \"ztunnel-restart\":\n // the ztunnel pod of each node goes with its grace. The flow of each client crosses the ztunnel\n // of its own node, thus each client meets the restart. The restore waits for the DaemonSet\n return {\n inject: [{ do: \"kubectl\", args: [\"-n\", \"istio-system\", \"delete\", \"pod\", \"-l\", ZTUNNEL_PODS, \"--wait=false\"] }],\n hold: null,\n heal: [],\n restore: [{ do: \"kubectl\", args: [\"-n\", \"istio-system\", \"rollout\", \"status\", \"daemonset/ztunnel\", \"--timeout=300s\"] }],\n };\n }\n};\n\n/** This function tells if a trigger stops a pod of the service with its grace: a deletion, an eviction, a scale-in or a rollout. */\nexport const deletes = (cell: KindCell): boolean =>\n [\"pod-deletion\", \"rolling-update\", \"node-drain\", \"scale-in\"].includes(cell.trigger.kind) ||\n (cell.template === \"T07\" && cell.trigger.kind === \"freeze-process\");\n\n/** These are the names of the objects of a cell that a plan names. */\nexport const OBJECTS: { readonly server: typeof SERVER; readonly client: typeof CLIENT; readonly redis: typeof REDIS } = {\n server: SERVER,\n client: CLIENT,\n redis: REDIS,\n};\n","import { execFileSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/** This folder is the root of the package, and its `image/` folder has the files of the image. */\nconst PACKAGE = fileURLToPath(new URL(\"../\", import.meta.url));\n/** This folder is the root of the repository, when the package is in the workspace of the repository. */\nconst REPO = fileURLToPath(new URL(\"../../../\", import.meta.url));\n/** These are the packages of this repository that the image holds, in the sequence of their dependencies. */\nexport const WORKSPACE_PACKAGES: readonly [\"quantity\", \"algebra\", \"service\"] = [\"quantity\", \"algebra\", \"service\"];\n/** These are the files of the image, by their name in the context and their source in the package. */\nconst FILES: ReadonlyArray<readonly [string, string]> = [\n [\"Dockerfile\", \"image/Dockerfile\"],\n [\"package.json\", \"image/package.json\"],\n [\"npm-lock.json\", \"image/npm-lock.json\"],\n [\"app.mjs\", \"image/app.mjs\"],\n [\"client.mjs\", \"image/client.mjs\"],\n [\"client-grpc.mjs\", \"image/client-grpc.mjs\"],\n [\"agent.mjs\", \"image/agent.mjs\"],\n [\"watch.mjs\", \"image/watch.mjs\"],\n [\"compose.ts\", \"src/compose.ts\"],\n [\"backends.ts\", \"src/backends.ts\"],\n [\"conntrack.ts\", \"src/conntrack.ts\"],\n [\"drops.ts\", \"src/drops.ts\"],\n [\"eds.ts\", \"src/eds.ts\"],\n [\"lds.ts\", \"src/lds.ts\"],\n [\"client-lines.ts\", \"src/client-lines.ts\"],\n];\n\nconst shell = process.platform === \"win32\";\n\n/** This function gives each file of a staged context, with its path in the context, in a stable sequence. */\nconst filesOf = (root: string, at = \"\"): string[] =>\n readdirSync(join(root, at), { withFileTypes: true })\n .flatMap((entry) => (entry.isDirectory() ? filesOf(root, join(at, entry.name)) : [join(at, entry.name)]))\n .sort();\n\n/** This function gives the tag of a staged context from its content: the name, then 12 digits of a sha256. */\nexport const contentTag = (name: string, root: string): string => {\n const hash = createHash(\"sha256\");\n for (const file of filesOf(root)) {\n hash.update(file.replaceAll(\"\\\\\", \"/\"));\n hash.update(readFileSync(join(root, file)));\n }\n return `${name}:${hash.digest(\"hex\").slice(0, 12)}`;\n};\n\n/** This function tells if the package is in the workspace of its repository. From npm, it is not. */\nconst inWorkspace = (): boolean =>\n existsSync(join(REPO, \"pnpm-workspace.yaml\")) && WORKSPACE_PACKAGES.every((name) => existsSync(join(REPO, \"packages\", name, \"package.json\")));\n\n/**\n * This function gives the folder of each installed package that the image holds. Node finds a\n * package from the folder of a package that depends on it. The service depends on the algebra,\n * thus the search starts with the service.\n */\nconst installedFolders = (): Map<string, string> => {\n const folders = new Map<string, string>();\n for (const name of [...WORKSPACE_PACKAGES].reverse()) {\n const found = [PACKAGE, ...folders.values()].flatMap((from) => {\n try {\n return [dirname(realpathSync(createRequire(join(from, \"package.json\")).resolve(`@mark1russell7/${name}/package.json`)))];\n } catch {\n return [];\n }\n })[0];\n if (found === undefined) throw new Error(`@mark1russell7/${name} is not installed with @mark1russell7/kind`);\n folders.set(name, found);\n }\n return folders;\n};\n\n/**\n * This function stages the build context of the lab image. In the workspace, it builds the packages\n * of the repository and packs each one as npm gets it. From npm, it packs the installed packages.\n * Then it copies the files of the image.\n */\nexport const stageLabImage = (): string => {\n const context = mkdtempSync(join(tmpdir(), \"systems-kind-lab-\"));\n const packed = join(context, \"workspace\");\n mkdirSync(packed);\n if (inWorkspace()) {\n const filters = WORKSPACE_PACKAGES.flatMap((name) => [\"--filter\", `@mark1russell7/${name}`]);\n execFileSync(\"pnpm\", [...filters, \"build\"], { cwd: REPO, shell, stdio: \"ignore\" });\n for (const name of WORKSPACE_PACKAGES)\n execFileSync(\"pnpm\", [\"pack\", \"--pack-destination\", packed], { cwd: join(REPO, \"packages\", name), shell, stdio: \"ignore\" });\n } else {\n for (const folder of installedFolders().values())\n execFileSync(\"npm\", [\"pack\", \"--pack-destination\", packed, \"--ignore-scripts\"], { cwd: folder, shell, stdio: \"ignore\" });\n }\n for (const [at, from] of FILES) copyFileSync(join(PACKAGE, from), join(context, at));\n return context;\n};\n\nlet built: Promise<string> | null = null;\n\n/**\n * This function builds the lab image one time for each process, and gives its tag. Docker keeps its\n * layers between builds. A build that fails leaves the cache. Thus a later call builds again after a\n * fault of Docker or of the network.\n */\nexport const labImage = (): Promise<string> => {\n if (built !== null) return built;\n const building = (async () => {\n const context = stageLabImage();\n try {\n const tag = contentTag(\"systems-kind-lab\", context);\n const { GenericContainer } = await import(\"testcontainers\");\n await GenericContainer.fromDockerfile(context).build(tag, { deleteOnExit: false });\n return tag;\n } finally {\n rmSync(context, { recursive: true, force: true });\n }\n })();\n // a failed build does not stay: the next caller tries again\n building.catch(() => {\n if (built === building) built = null;\n });\n built = building;\n return building;\n};\n","import { writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport type { KindCell, MeshFlavor } from \"./cell.ts\";\nimport type { Cluster } from \"./cluster.ts\";\nimport { command } from \"./cluster.ts\";\nimport { outboundCluster } from \"./eds.ts\";\nimport { ZTUNNEL_PODS } from \"./faults.ts\";\nimport { SERVER } from \"./names.ts\";\nimport { ISTIOCTL, ensureTool } from \"./tools.ts\";\n\n/**\n * This module installs Istio 1.31.3 in a kind cluster with istioctl, from images pinned by digest.\n * The release notes of 1.31.2 and 1.31.3 change no default that a cell of T15 reads. Each value\n * of the install that is not a default of Istio is in `istioOperator`, with its reason.\n */\n\n/** This version is the release of Istio of the lab: the tag of istioctl and of each image. */\nexport const ISTIO_VERSION = \"1.31.3\";\n\n/**\n * These are the images of Istio, pinned by the digest of their index on Docker Hub (read on\n * 2026-10-24). `gcr.io/istio-release` has no final tag of 1.31. The lab pulls each one by its digest,\n * tags it with that digest (`localPin`), and loads it into each node. Thus no node pulls from a\n * registry. The flavor `sidecar` needs istiod and the proxy. The flavor `ambient` also needs\n * istio-cni and ztunnel (`MESH_IMAGES`).\n */\nexport const ISTIO_IMAGES: { readonly pilot: string; readonly proxy: string; readonly cni: string; readonly ztunnel: string } = {\n pilot: \"istio/pilot:1.31.3@sha256:6ad1e816c4d4767ba3f30a6286a96d121851ddb23d33e20736758acb5f3f4ea9\",\n proxy: \"istio/proxyv2:1.31.3@sha256:eb74eacea98d3fb1e239ea84016085155478586a554158676b9b67d960151084\",\n cni: \"istio/install-cni:1.31.3@sha256:e98782038634ef4fadaf78b53dd8fa4134fda3d65838ead5fc1edef245451c0f\",\n ztunnel: \"istio/ztunnel:1.31.3@sha256:2c27371b3139ecbf4f5a017354bd540889482441712e88cd8e654ead5a3eb5b2\",\n};\n\n/** These are the images that each flavor installs, by their names in `ISTIO_IMAGES`. */\nexport const MESH_IMAGES: Readonly<Record<MeshFlavor, ReadonlyArray<keyof typeof ISTIO_IMAGES>>> = {\n sidecar: [\"pilot\", \"proxy\"],\n ambient: [\"pilot\", \"proxy\", \"cni\", \"ztunnel\"],\n};\n\n/**\n * This format is the JSON access log of each sidecar. Each field is a command of the formatter of\n * Envoy v1.39.4. The fields give the status, the response flags and their details, and the gRPC\n * status as a number. They also give the tries of the router, the upstream host and cluster, and\n * the reasons of a failed connection. The watcher reads `code`, `flags`, `grpc` and `attempts`\n * (`image/watch.mjs`).\n */\nexport const ACCESS_LOG_FORMAT: Readonly<Record<string, string>> = {\n start: \"%START_TIME%\",\n code: \"%RESPONSE_CODE%\",\n flags: \"%RESPONSE_FLAGS%\",\n details: \"%RESPONSE_CODE_DETAILS%\",\n grpc: \"%GRPC_STATUS_NUMBER%\",\n attempts: \"%UPSTREAM_REQUEST_ATTEMPT_COUNT%\",\n host: \"%UPSTREAM_HOST%\",\n cluster: \"%UPSTREAM_CLUSTER%\",\n local: \"%DOWNSTREAM_LOCAL_ADDRESS%\",\n remote: \"%DOWNSTREAM_REMOTE_ADDRESS%\",\n duration: \"%DURATION%\",\n failure: \"%UPSTREAM_TRANSPORT_FAILURE_REASON%\",\n termination: \"%CONNECTION_TERMINATION_DETAILS%\",\n};\n\n/** These are the local tags of the images, after the pull by digest. The flavor `ambient` needs `cni` and `ztunnel`. */\nexport interface MeshImages {\n readonly pilot: string;\n readonly proxy: string;\n readonly cni?: string | undefined;\n readonly ztunnel?: string | undefined;\n}\n\n/**\n * This function gives the IstioOperator of a flavor. The values that are not the defaults of Istio:\n * - The profile `minimal` is istiod only. The profile `default` also has an ingress gateway, which no cell uses.\n * - Each image is a full reference, thus the charts use it as it is (`contains \"/\"` in the\n * templates of istiod and of the injector). The init container of the injector has the image of the proxy too (`proxy_init`).\n * - `imagePullPolicy: Never`: each image is on each node. An image that the lab did not load fails the install at once.\n * - istiod requests 256 MiB, not 2 GiB, and has one replica with no autoscaler. Each node of kind\n * reports the whole runner, and no cell scales istiod.\n * - The access log of each sidecar goes to its output as JSON (`ACCESS_LOG_FORMAT`).\n *\n * The flavor `ambient` has the profile `ambient`: istiod, istio-cni and ztunnel, and no gateway\n * (manifests/profiles/ambient.yaml of Istio 1.31.3). Its two other images are full references too\n * (templates/daemonset.yaml of the chart of istio-cni, lines 93–96, and of ztunnel, lines 65–68).\n * The chart of istio-cni takes the pull policy of `global`. The template of ztunnel reads only its own\n * `imagePullPolicy`, thus the operator sets it there too.\n */\nexport const istioOperator = (flavor: MeshFlavor, images: MeshImages): Record<string, unknown> => {\n const ambient = flavor === \"ambient\";\n if (ambient && (images.cni === undefined || images.ztunnel === undefined)) throw new Error(\"the flavor ambient needs the images of istio-cni and ztunnel\");\n return {\n apiVersion: \"install.istio.io/v1alpha1\",\n kind: \"IstioOperator\",\n metadata: { name: \"lab\", namespace: \"istio-system\" },\n spec: {\n profile: ambient ? \"ambient\" : \"minimal\",\n hub: \"docker.io/istio\",\n tag: ISTIO_VERSION,\n meshConfig: {\n accessLogFile: \"/dev/stdout\",\n accessLogEncoding: \"JSON\",\n accessLogFormat: JSON.stringify(ACCESS_LOG_FORMAT),\n },\n values: {\n global: { imagePullPolicy: \"Never\", proxy: { image: images.proxy }, proxy_init: { image: images.proxy } },\n pilot: { image: images.pilot, autoscaleEnabled: false, replicaCount: 1, resources: { requests: { cpu: \"500m\", memory: \"256Mi\" } } },\n ...(ambient ? { cni: { image: images.cni }, ztunnel: { image: images.ztunnel, imagePullPolicy: \"Never\" } } : {}),\n },\n },\n };\n};\n\n/** These are the workloads of each flavor in istio-system that the install waits for. */\nexport const MESH_WORKLOADS: Readonly<Record<MeshFlavor, readonly string[]>> = {\n sidecar: [\"deployment/istiod\"],\n ambient: [\"deployment/istiod\", \"daemonset/istio-cni-node\", \"daemonset/ztunnel\"],\n};\n\n/**\n * This function installs the mesh of a flavor in a cluster with `istioctl install` and the operator\n * of the flavor. Then it waits for each workload of the flavor. The kubeconfig of the cluster goes to\n * istioctl. When the install fails, the error has the pods of istio-system and the last lines of the\n * log of istio-cni. Thus the log of the job shows the cause, for example a module that the kernel of\n * the runner does not have.\n */\nexport const installMesh = async (cluster: Cluster, flavor: MeshFlavor, images: MeshImages): Promise<void> => {\n const istioctl = await ensureTool(ISTIOCTL);\n const file = join(cluster.dir, `istio-${flavor}.json`);\n writeFileSync(file, `${JSON.stringify(istioOperator(flavor, images), null, 2)}\\n`);\n try {\n await command(istioctl, [\"install\", \"--kubeconfig\", cluster.kubeconfig, \"-y\", \"-f\", file, \"--readiness-timeout\", \"10m0s\"], { timeout: 900_000 });\n for (const workload of MESH_WORKLOADS[flavor]) await cluster.kubectl([\"-n\", \"istio-system\", \"rollout\", \"status\", workload, \"--timeout=300s\"]);\n } catch (error) {\n const state = async (args: readonly string[]): Promise<string> =>\n (await cluster.kubectl([\"-n\", \"istio-system\", ...args]).catch((why: unknown) => String(why))).slice(-4000);\n const pods = await state([\"get\", \"pods\", \"-o\", \"wide\"]);\n const cni = flavor === \"ambient\" ? `\\nthe log of istio-cni:\\n${await state([\"logs\", \"daemonset/istio-cni-node\", \"--tail=60\", \"--all-containers\"])}` : \"\";\n throw new Error(`the install of Istio ${flavor} failed: ${String(error)}\\nthe pods of istio-system:\\n${pods}${cni}`);\n }\n};\n\n/** This function gives the key of the mesh of a cell for its cluster: its flavor, or `none`. */\nexport const meshKey = (cell: Pick<KindCell, \"mesh\">): MeshFlavor | \"none\" => cell.mesh?.flavor ?? \"none\";\n\n/** This function gives the outbound cluster of the server of a cell in each client sidecar: the port 80 of its Service. */\nexport const serverCluster = (namespace: string): string => outboundCluster(SERVER, namespace, 80);\n\n/** This time is the interval of the EDS poll of each client sidecar, the same as the poll of the rules. */\nexport const EDS_EVERY: Quantity = { value: 50, unit: \"ms\" };\n\n/** This time is the interval of the LDS poll of each server sidecar, the same as the poll of EDS. */\nexport const LDS_EVERY: Quantity = { value: 50, unit: \"ms\" };\n\n/**\n * This function tells if the watcher follows each ztunnel pod and its log for a cell: a cell in the\n * flavor `ambient`. A cell of sidecar pods in that flavor needs the lines of the ztunnel pods too.\n * They show the restart of ztunnel, which its pods do not see.\n */\nexport const followsZtunnel = (cell: Pick<KindCell, \"mesh\">): boolean => cell.mesh?.flavor === \"ambient\";\n\n/**\n * This function gives the arguments of kubectl that set the environment of ztunnel for a cell, or\n * remove it (`undo`). The variables go in the sequence of their names.\n */\nexport const ztunnelEnvArgs = (env: Readonly<Record<string, string>>, undo: boolean): string[] => [\n \"-n\",\n \"istio-system\",\n \"set\",\n \"env\",\n \"daemonset/ztunnel\",\n ...Object.keys(env)\n .sort()\n .map((key) => (undo ? `${key}-` : `${key}=${env[key]!}`)),\n];\n\n/**\n * This function sets the environment of ztunnel for a cell, or removes it (`undo`). Then it waits for\n * the rollout of the DaemonSet, and until no old ztunnel pod is left. An old pod drains for its\n * deadline after the rollout replaces it. Thus no run starts beside a ztunnel pod that is going.\n */\nexport const setZtunnelEnv = async (cluster: Cluster, env: Readonly<Record<string, string>>, undo: boolean): Promise<void> => {\n await cluster.kubectl(ztunnelEnvArgs(env, undo));\n await cluster.kubectl([\"-n\", \"istio-system\", \"rollout\", \"status\", \"daemonset/ztunnel\", \"--timeout=300s\"]);\n const until = Date.now() + 120_000;\n for (;;) {\n const listed = JSON.parse(await cluster.kubectl([\"-n\", \"istio-system\", \"get\", \"pods\", \"-l\", ZTUNNEL_PODS, \"-o\", \"json\"])) as {\n items: Array<{ metadata: { deletionTimestamp?: string } }>;\n };\n if (listed.items.every((item) => item.metadata.deletionTimestamp === undefined)) return;\n if (Date.now() > until) throw new Error(\"an old ztunnel pod is still going 120 s after the rollout\");\n await new Promise((resolve) => setTimeout(resolve, 2000));\n }\n};\n\n/** This function tells if the lab polls the LDS updates of the server sidecars of a cell: a policy in a sidecar mesh. */\nexport const pollsListeners = (cell: Pick<KindCell, \"mesh\" | \"trigger\">): boolean => cell.mesh?.mode === \"sidecar\" && cell.trigger.kind === \"authorization-policy\";\n","// This module has no imports: the agent of the lab image imports it as it is.\n\n/**\n * This filter of RE2 selects the two counters of the listener manager of Envoy that an update of LDS\n * moves (`/stats?filter=`, `source/server/admin/stats_params.cc` of Envoy v1.39.4). An update of a\n * listener counts `listener_modified`. An update in place, of its filter chains only, also counts\n * `listener_in_place_updated` (`listener_manager_impl.cc:644`, `:692`).\n */\nexport const LDS_FILTER = \"^listener_manager\\\\.listener_(in_place_updated|modified)$\";\n\n/** The listener counts are the two counters of LDS of one sidecar: each update (`mod`), and each update in place (`inPlace`). */\nexport interface ListenerCounts {\n readonly mod: number;\n readonly inPlace: number;\n}\n\n/**\n * This function gives the listener counts of the text of `/stats`, one `name: value` on each line\n * (`stats_render.cc:15`). It gives null when the text has no counter `listener_modified`.\n */\nexport const listenerCountsOf = (text: string): ListenerCounts | null => {\n let mod: number | null = null;\n let inPlace = 0;\n for (const line of text.split(\"\\n\")) {\n const match = /^listener_manager\\.listener_(in_place_updated|modified): (\\d+)$/.exec(line.trim());\n if (match === null) continue;\n if (match[1] === \"modified\") mod = Number(match[2]);\n else inPlace = Number(match[2]);\n }\n return mod === null ? null : { mod, inPlace };\n};\n","// The parser of the conntrack table of a node. The agent of each node reads the table with it, and\n// the image copies this file, thus it imports nothing.\n\n/** A flow is one conntrack entry of TCP: its state, its life in seconds, its original tuple and the source of its replies. */\nexport interface ConntrackFlow {\n readonly state: string;\n readonly ttl: number;\n /** This address is the original source of the flow, as `ip:port`. */\n readonly from: string;\n /** This address is the original destination of the flow, as `ip:port`: a ClusterIP before its DNAT. */\n readonly to: string;\n /** This address is the source of the replies, as `ip:port`: the backend that the DNAT of the entry chose. */\n readonly reply: string;\n /** This flag tells that no packet came back on the flow. */\n readonly unreplied: boolean;\n}\n\n/** An entry is one conntrack entry of a TCP flow to the ClusterIP. It has a state, a life in seconds, a client and the backend of its NAT. */\nexport interface ConntrackEntry {\n readonly state: string;\n readonly ttl: number;\n /** This address is the original source of the flow: the client, as `ip:port`. */\n readonly client: string;\n /** This address is the source of the replies: the backend that the DNAT of the entry chose, as `ip:port`. */\n readonly backend: string;\n /** This flag tells that no packet came back on the flow. */\n readonly unreplied: boolean;\n}\n\n/**\n * These states keep the NAT of an entry for a new SYN on the same port (nf_conntrack_proto_tcp.c at\n * v6.8). A SYN on an entry in TIME_WAIT removes the entry and looks up again (:1011-1027), thus that\n * SYN gets a backend from the rules. In each other state, the SYN goes to the backend of the entry.\n * A CLOSE that a reset from the server made does not reopen either.\n */\nexport const KEEPS_NAT: ReadonlySet<string> = new Set([\"SYN_SENT\", \"SYN_SENT2\", \"SYN_RECV\", \"ESTABLISHED\", \"FIN_WAIT\", \"CLOSE_WAIT\", \"LAST_ACK\", \"CLOSE\"]);\n\n/**\n * These are the lives of an entry in the states of a handshake, in seconds\n * (nf_conntrack_tcp_timeout_syn_sent and _syn_recv). A retransmission of the SYN does not renew the\n * life of SYN_SENT (nf_conntrack_proto_tcp.c at v6.8). Thus the life of such an entry tells the age\n * of its first SYN.\n */\nexport const HANDSHAKE_LIFE: Readonly<Record<string, number>> = { SYN_SENT: 120, SYN_RECV: 60 };\n\n/** This form is one TCP line of `/proc/net/nf_conntrack` or `conntrack -L`. It has the timeout and state, the original tuple, the flags and counters, then the reply tuple. */\nconst LINE = /\\btcp\\s+\\d+\\s+(\\d+)\\s+(\\S+)\\s+src=(\\S+) dst=(\\S+) sport=(\\d+) dport=(\\d+)(.*?)\\bsrc=(\\S+) dst=\\S+ sport=(\\d+) dport=\\d+/;\n\n/** This function gives the TCP flows of a dump of the conntrack table. */\nexport const flowsOf = (text: string): ConntrackFlow[] => {\n const out: ConntrackFlow[] = [];\n for (const line of text.split(\"\\n\")) {\n const match = LINE.exec(line);\n if (match === null) continue;\n out.push({\n ttl: Number(match[1]),\n state: match[2]!,\n from: `${match[3]}:${match[5]}`,\n to: `${match[4]}:${match[6]}`,\n reply: `${match[8]}:${match[9]}`,\n unreplied: match[7]!.includes(\"[UNREPLIED]\"),\n });\n }\n return out;\n};\n\n/** This function gives the TCP entries of a dump of the conntrack table whose original destination is the ClusterIP and port. */\nexport const entriesOf = (text: string, ip: string, port: number): ConntrackEntry[] =>\n flowsOf(text)\n .filter((flow) => flow.to === `${ip}:${port}`)\n .map((flow) => ({ state: flow.state, ttl: flow.ttl, client: flow.from, backend: flow.reply, unreplied: flow.unreplied }));\n\n/**\n * This function gives the entries that keep their NAT to a pod that is not a backend of the rules.\n * A new SYN from the client port of such an entry goes to that pod, whatever the rules say.\n */\nexport const staleOf = (entries: readonly ConntrackEntry[], backends: readonly string[]): ConntrackEntry[] =>\n entries.filter((entry) => KEEPS_NAT.has(entry.state) && !backends.includes(entry.backend));\n\n/** This function gives the count of entries that `conntrack -D` deleted, from its last line: \"N flow entries have been deleted\". */\nexport const deletedOf = (text: string): number => {\n const match = /(\\d+) flow entr(?:y|ies) ha(?:s|ve) been deleted/.exec(text);\n if (match === null) throw new Error(`conntrack -D gave no count: ${text.trim().slice(0, 200)}`);\n return Number(match[1]);\n};\n\n/**\n * This function gives the flows whose handshake stopped `age` seconds ago or more. Such a flow is\n * a SYN with no reply (SYN_SENT), or a SYN and its SYN-ACK with no ACK (SYN_RECV). It is a\n * connection that times out. On the node of the client, its reply address is the backend of its\n * DNAT. On the node of that backend, a SYN_SENT tells that the backend did not answer. There, a\n * SYN_RECV tells that its answer did not come back.\n */\nexport const stalledOf = (flows: readonly ConntrackFlow[], age: number): ConntrackFlow[] =>\n flows.filter((flow) => {\n const life = HANDSHAKE_LIFE[flow.state];\n if (life === undefined || (flow.state === \"SYN_SENT\" && !flow.unreplied)) return false;\n return flow.ttl <= life - age;\n });\n","import type { StartedTestContainer } from \"testcontainers\";\nimport { harnessImage } from \"@mark1russell7/harness\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { ServiceRule } from \"./backends.ts\";\nimport { deletedOf } from \"./conntrack.ts\";\nimport type { Cluster } from \"./cluster.ts\";\nimport { command } from \"./cluster.ts\";\nimport type { Line } from \"./observe.ts\";\n\nexport type { Line } from \"./observe.ts\";\n\n/** A stamp brackets an act on CLOCK_MONOTONIC, in milliseconds. */\nexport interface Stamp {\n readonly t0: number;\n readonly t1: number;\n}\n\n/** These kinds of kernel record stay in the raw record of a run. The lab counts the other kinds and drops them. */\nexport const KEPT_KERNEL: ReadonlySet<string> = new Set([\"rst_tx\", \"rst_rx\", \"rst_v4\", \"error\", \"retrans\", \"begin\", \"fail\"]);\n\n/** An EDS spec is the cluster that the EDS pollers read, their interval, and each client pod with its node and a PID in it. */\nexport interface EdsSpec {\n readonly cluster: string;\n readonly every: Quantity;\n readonly pods: ReadonlyArray<{ readonly node: string; readonly pod: string; readonly pid: number }>;\n}\n\n/** An LDS spec is the interval of the LDS pollers, and each server pod with its node and a PID in it. */\nexport interface LdsSpec {\n readonly every: Quantity;\n readonly pods: ReadonlyArray<{ readonly node: string; readonly pod: string; readonly pid: number }>;\n}\n\n/** These are the sidecars of one node: the agent of the lab and the injector of the harness, in the PID namespace of the node. */\ninterface Sidecars {\n readonly agent: StartedTestContainer;\n readonly injector: StartedTestContainer;\n}\n\n/** A kind lab is the instruments of one cluster: a sidecar pair on each node, the watcher of the API and the prober. */\nexport interface KindLab {\n readonly cluster: Cluster;\n readonly image: string;\n /** These are the lines since the last `take()`. */\n readonly lines: Line[];\n /** This record counts the kernel records that the lab dropped, by kind. */\n readonly dropped: Record<string, number>;\n /** This method gives the names of the containers that the lab started: they are not the lab containers of another agent. */\n own(): string[];\n /** This method gives the lines since the last call, and starts a new buffer. */\n take(): Line[];\n /** This method adds a listener of each line, and gives the function that removes it. */\n listen(listener: (line: Line) => void): () => void;\n /**\n * This method starts the watcher of the API on the namespaces. With `ztunnel`, it also follows the\n * log of each ztunnel in istio-system: lines `ztunnel-pod` and `ztunnel`, which no tracker of the\n * cell reads.\n */\n watch(namespaces: readonly string[], options?: { readonly ztunnel?: boolean | undefined }): Promise<void>;\n probe(netns: readonly number[]): Promise<void>;\n stopProbe(): Promise<void>;\n /** This method asks each agent to poll the backends of a Service in the rules of its node, `every` apart, or to stop with null. */\n rules(rule: (ServiceRule & { readonly every: Quantity }) | null): Promise<void>;\n /**\n * This method asks each agent to poll the sidecar of each client pod of its node, `every` apart.\n * Each poll gives the hosts of one cluster. With null, the polls stop. A pod gives the PID of one\n * of its processes, and the agent enters the network namespace of that PID.\n */\n eds(spec: EdsSpec | null): Promise<void>;\n /**\n * This method asks each agent to poll the sidecar of each server pod of its node, `every` apart.\n * Each poll gives the counters of LDS updates of that sidecar (`lds.ts`). With null, the polls stop.\n */\n lds(spec: LdsSpec | null): Promise<void>;\n act(node: string, spec: Readonly<Record<string, unknown>>): Promise<Stamp>;\n inject(node: string, pid: number, args: readonly string[]): Promise<Stamp>;\n /**\n * This method gives the rules of one table of iptables in the network namespace of a PID, with\n * their counters (`iptables-save -c`). The injector reads them. The method also adds them to the\n * lines as a line `tables`: evidence of which rules matched.\n */\n tables(node: string, pid: number, table: string): Promise<string>;\n now(): Promise<number>;\n pidOf(node: string, namespace: string, pod: string, container: string): Promise<number>;\n netnsOf(node: string, pid: number): Promise<number>;\n /** This method gives the path of the cgroup of a process, as the agent reaches it through the root of the node. */\n cgroupOf(node: string, pid: number): Promise<string>;\n /**\n * This method deletes the conntrack entries in SYN_SENT to a ClusterIP and port on each node, and\n * gives the count of each node. Such an entry is a SYN with no answer. It keeps its NAT for 120 s,\n * and a new SYN from the same port goes where it went.\n */\n forgetHandshakes(ip: string, port: number): Promise<Record<string, number>>;\n close(): Promise<void>;\n}\n\n/** This container class can share the process namespace of another container. */\nconst shared = async () => {\n const { GenericContainer } = await import(\"testcontainers\");\n return class extends GenericContainer {\n withPidMode(mode: string): this {\n (this as unknown as { hostConfig: { PidMode?: string } }).hostConfig.PidMode = mode;\n return this;\n }\n };\n};\n\n/** This function collects the JSON lines of a stream, and gives each one to `take`. */\nconst consume = (take: (line: Record<string, unknown>) => void) => (stream: NodeJS.ReadableStream) => {\n let partial = \"\";\n stream.on(\"data\", (chunk: Buffer | string) => {\n partial += chunk.toString();\n const split = partial.split(\"\\n\");\n partial = split.pop() ?? \"\";\n for (const text of split) {\n const trimmed = text.trim();\n if (!trimmed.startsWith(\"{\")) continue;\n try {\n take(JSON.parse(trimmed) as Record<string, unknown>);\n } catch {\n // a line that is not whole JSON is not a record\n }\n }\n });\n};\n\n/** This function gives a stamp of the injector of the harness, in nanoseconds, as milliseconds. */\nconst injectorStamp = (stdout: string): Stamp => {\n const stamp = JSON.parse(stdout.trim().split(\"\\n\").at(-1)!) as { t0: number | string; t1: number | string };\n return { t0: Number(BigInt(stamp.t0)) / 1e6, t1: Number(BigInt(stamp.t1)) / 1e6 };\n};\n\nconst output = async (container: StartedTestContainer, argv: readonly string[]): Promise<string> => {\n const result = await container.exec([...argv]);\n if (result.exitCode !== 0) throw new Error(`${argv.join(\" \")} exited ${result.exitCode}: ${result.output.trim().slice(0, 500)}`);\n return result.stdout;\n};\n\n/**\n * This function opens the instruments of a cluster. Each node gets the agent of the lab and the\n * injector of the harness, privileged and in the PID namespace of the node. Thus the agent signals\n * a process of a pod by its PID on the node. The injector enters the network namespace of a pod\n * with `nsenter`.\n */\nexport const openKindLab = async (cluster: Cluster, image: string): Promise<KindLab> => {\n const harness = await harnessImage();\n const Shared = await shared();\n const { GenericContainer } = await import(\"testcontainers\");\n let lines: Line[] = [];\n const dropped: Record<string, number> = {};\n const listeners = new Set<(line: Line) => void>();\n const push = (line: Line): void => {\n lines.push(line);\n for (const listener of listeners) listener(line);\n };\n const sidecars = new Map<string, Sidecars>();\n const opened: StartedTestContainer[] = [];\n let watcher: StartedTestContainer | null = null;\n let prober: StartedTestContainer | null = null;\n try {\n for (const node of cluster.nodes) {\n const agent = await new Shared(image)\n .withPidMode(`container:${node}`)\n .withPrivilegedMode()\n .withNetworkMode(\"none\")\n .withEnvironment({ NODE_NAME: node })\n .withCommand([\"node\", \"agent.mjs\", \"serve\"])\n .withLogConsumer(consume((line) => push({ ...(line as unknown as Line), node })))\n .start();\n opened.push(agent);\n const injector = await new Shared(harness)\n .withPidMode(`container:${node}`)\n .withPrivilegedMode()\n .withNetworkMode(\"none\")\n .start();\n opened.push(injector);\n sidecars.set(node, { agent, injector });\n }\n } catch (error) {\n for (const container of opened.reverse()) await container.stop({ timeout: 0 }).catch(() => undefined);\n throw error;\n }\n const sidecarOf = (node: string): Sidecars => {\n const found = sidecars.get(node);\n if (found === undefined) throw new Error(`the cluster has no node ${node}`);\n return found;\n };\n const firstNode = cluster.nodes[0]!;\n /**\n * This function writes the file of the pollers of one kind on each agent: `fields`, the interval\n * and the pods of its node. A node without a pod, or a spec of null, gets no file.\n */\n const pollersOf = async (\n kind: \"eds\" | \"lds\",\n fields: Readonly<Record<string, unknown>> | null,\n spec: { readonly every: Quantity; readonly pods: LdsSpec[\"pods\"] } | null,\n ): Promise<void> => {\n for (const [node, { agent }] of sidecars) {\n const pods = spec === null ? [] : spec.pods.filter((pod) => pod.node === node).map(({ pod, pid }) => ({ pod, pid }));\n // the agent reads the interval of its poll in milliseconds\n const file = spec === null || pods.length === 0 ? null : JSON.stringify({ ...fields, every: valueIn(spec.every, \"ms\"), pods });\n if (file === null) await output(agent, [\"rm\", \"-f\", `/tmp/${kind}.json`]);\n // a rename is atomic, thus the agent never reads a part of the file\n else await output(agent, [\"sh\", \"-c\", `printf '%s' '${file}' > /tmp/${kind}.json.partial && mv /tmp/${kind}.json.partial /tmp/${kind}.json`]);\n }\n };\n const lab: KindLab = {\n cluster,\n image,\n get lines() {\n return lines;\n },\n dropped,\n own() {\n return [...opened, ...(watcher === null ? [] : [watcher]), ...(prober === null ? [] : [prober])].map((container) => container.getName().replace(/^\\//, \"\"));\n },\n take() {\n const out = lines;\n lines = [];\n return out;\n },\n listen(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n async watch(namespaces, options) {\n if (watcher !== null) await watcher.stop({ timeout: 0 }).catch(() => undefined);\n const kube = await cluster.credentials();\n watcher = await new GenericContainer(image)\n .withNetworkMode(cluster.network)\n .withEnvironment({ KUBE: JSON.stringify(kube), NAMESPACES: namespaces.join(\",\"), ZTUNNEL: options?.ztunnel === true ? \"1\" : \"0\" })\n .withCommand([\"node\", \"watch.mjs\"])\n .withLogConsumer(consume((line) => push(line as unknown as Line)))\n .start();\n },\n async probe(netns) {\n await lab.stopProbe();\n let begun: (ok: boolean, why: string) => void = () => {};\n const ready = new Promise<void>((resolve, reject) => {\n begun = (ok, why) => (ok ? resolve() : reject(new Error(`the prober failed: ${why}`)));\n });\n prober = await new GenericContainer(harness)\n .withPrivilegedMode()\n .withNetworkMode(\"none\")\n .withCommand([\"/opt/harness/prober\", ...netns.map(String)])\n .withLogConsumer(\n consume((record) => {\n const ev = String(record[\"ev\"]);\n if (ev === \"begin\") begun(true, \"\");\n if (ev === \"fail\") begun(false, String(record[\"why\"]));\n if (!KEPT_KERNEL.has(ev) && ev !== \"clock\") {\n dropped[ev] = (dropped[ev] ?? 0) + 1;\n return;\n }\n const mono = Number(record[\"mono\"] ?? 0);\n push({ ...(record as unknown as Line), ev: `kernel.${ev}`, t: mono / 1e6 });\n }),\n )\n .start();\n await Promise.race([\n ready,\n new Promise((_, reject) => setTimeout(() => reject(new Error(\"no begin line from the prober in 60 s\")), 60_000)),\n ]);\n },\n async stopProbe() {\n if (prober !== null) await prober.stop({ timeout: 0 }).catch(() => undefined);\n prober = null;\n },\n async rules(rule) {\n // the agent reads the interval of its poll in milliseconds\n const file = rule === null ? null : JSON.stringify({ ...rule, every: valueIn(rule.every, \"ms\") });\n for (const { agent } of sidecars.values()) {\n if (file === null) await output(agent, [\"rm\", \"-f\", \"/tmp/rules.json\"]);\n else await output(agent, [\"sh\", \"-c\", `printf '%s' '${file}' > /tmp/rules.json`]);\n }\n },\n async eds(spec) {\n await pollersOf(\"eds\", spec === null ? null : { cluster: spec.cluster }, spec);\n },\n async lds(spec) {\n await pollersOf(\"lds\", {}, spec);\n },\n async forgetHandshakes(ip, port) {\n const out: Record<string, number> = {};\n for (const [node, { agent }] of sidecars) {\n const argv = [\"nsenter\", \"-t\", \"1\", \"-m\", \"-n\", \"--\", \"conntrack\", \"-D\", \"-p\", \"tcp\", \"--orig-dst\", ip, \"--dport\", String(port), \"--state\", \"SYN_SENT\"];\n // the tool writes its count to stderr, and exits 1 when it deletes no entry\n const result = await agent.exec(argv);\n if (result.exitCode !== 0 && result.exitCode !== 1) throw new Error(`${argv.join(\" \")} exited ${result.exitCode}: ${result.output.trim().slice(0, 300)}`);\n out[node] = deletedOf(result.output);\n }\n return out;\n },\n async act(node, spec) {\n const stdout = await output(sidecarOf(node).agent, [\"node\", \"agent.mjs\", \"act\", JSON.stringify(spec)]);\n const result = JSON.parse(stdout.trim().split(\"\\n\").at(-1)!) as { ok: boolean; t0: number; t1: number; why?: string };\n if (!result.ok) throw new Error(`the act ${JSON.stringify(spec)} on ${node} failed: ${result.why}`);\n push({ ev: \"act\", t: result.t0, t1: result.t1, node, spec });\n return { t0: result.t0, t1: result.t1 };\n },\n async inject(node, pid, args) {\n const stdout = await output(sidecarOf(node).injector, [\"nsenter\", \"-t\", String(pid), \"-n\", \"/opt/harness/inject\", ...args]);\n const stamp = injectorStamp(stdout);\n push({ ev: \"act\", t: stamp.t0, t1: stamp.t1, node, spec: { op: \"inject\", pid, args } });\n return stamp;\n },\n async tables(node, pid, table) {\n const t = await lab.now();\n const text = await output(sidecarOf(node).injector, [\"nsenter\", \"-t\", String(pid), \"-n\", \"iptables-save\", \"-c\", \"-t\", table]);\n push({ ev: \"tables\", t, node, pid, table, text: text.slice(0, 8000) });\n return text;\n },\n async now() {\n return (await lab.act(firstNode, { op: \"now\" })).t0;\n },\n async pidOf(node, namespace, pod, container) {\n const ids = (\n await cluster.exec(node, [\n \"crictl\",\n \"ps\",\n \"-q\",\n \"--state\",\n \"running\",\n \"--label\",\n `io.kubernetes.pod.namespace=${namespace}`,\n \"--label\",\n `io.kubernetes.pod.name=${pod}`,\n \"--name\",\n `^${container}$`,\n ])\n )\n .split(/\\s+/)\n .filter((id) => id !== \"\");\n if (ids.length !== 1) throw new Error(`${namespace}/${pod}/${container} on ${node} has ${ids.length} running containers, not 1`);\n const info = JSON.parse(await cluster.exec(node, [\"crictl\", \"inspect\", ids[0]!])) as { info?: { pid?: number } };\n const pid = info.info?.pid;\n if (pid === undefined || !(pid > 1)) throw new Error(`crictl gives no PID for ${namespace}/${pod}/${container}`);\n return pid;\n },\n async netnsOf(node, pid) {\n const link = (await cluster.exec(node, [\"readlink\", `/proc/${pid}/ns/net`])).trim();\n const inode = /^net:\\[(\\d+)\\]$/.exec(link)?.[1];\n if (inode === undefined) throw new Error(`not a network namespace: ${link}`);\n return Number(inode);\n },\n async cgroupOf(node, pid) {\n const text = (await cluster.exec(node, [\"cat\", `/proc/${pid}/cgroup`])).trim();\n const path = /^0::(\\S+)$/m.exec(text)?.[1];\n // an empty or a short path is the root of the node: a write there throttles or freezes each pod of the node\n if (path === undefined || !/\\/cri-containerd-[0-9a-f]{64}\\.scope$/.test(path))\n throw new Error(`the cgroup of ${pid} on ${node} is not the scope of one container: ${text}`);\n return `/proc/1/root/sys/fs/cgroup${path}`;\n },\n async close() {\n await lab.stopProbe();\n if (watcher !== null) await watcher.stop({ timeout: 0 }).catch(() => undefined);\n watcher = null;\n for (const container of opened.reverse()) await container.stop({ timeout: 0 }).catch(() => undefined);\n },\n };\n return lab;\n};\n\n/** This function pins a Docker image by digest under a local tag that holds the digest, for `kind load` and `imagePullPolicy: Never`. */\nexport const localPin = async (image: string): Promise<string> => {\n const match = /^([^@]+?)(?::([^@:/]+))?@sha256:([0-9a-f]{64})$/.exec(image);\n if (match === null) throw new Error(`${image} is not pinned by a digest`);\n const [, repository, tag, digest] = match;\n const local = `systems-lab/${repository!.split(\"/\").at(-1)}:${tag ?? \"pinned\"}-${digest!.slice(0, 12)}`;\n const present = await command(\"docker\", [\"image\", \"inspect\", \"--format\", \"{{json .RepoDigests}}\", `${repository}@sha256:${digest}`]).catch(() => \"\");\n if (!present.includes(digest!)) await command(\"docker\", [\"pull\", `${repository}@sha256:${digest}`], { timeout: 900_000 });\n await command(\"docker\", [\"tag\", `${repository}@sha256:${digest}`, local]);\n return local;\n};\n","import { createHash } from \"node:crypto\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { KindCell, KubeletProbe, MeshMode, Stack } from \"./cell.ts\";\nimport { CLIENT, HEALTH_PORT, LOG_LABEL, PORT, REDIS, SERVER } from \"./names.ts\";\n\n/**\n * This module generates the Kubernetes objects of a cell. They are its namespace, its Redis, the\n * server as a Deployment behind a Service, and one client on each node. The server is the service\n * or grpc-go, by the stack of the cell. Each setting of the pod comes from the cell. The rollout\n * strategy and the probe fields state the defaults of Kubernetes as values.\n */\n\n/** This function gives the stack of a cell: the service, when the cell names no stack. */\nexport const stackOf = (cell: Pick<KindCell, \"stack\">): Stack => cell.stack ?? \"service\";\n\nexport { CLIENT, HEALTH_PORT, LOG_LABEL, PORT, REDIS, SERVER } from \"./names.ts\";\n\n/** This image is Redis 7.4, the stock target of the harness, pinned by digest. */\nexport const REDIS_IMAGE = \"redis:7.4@sha256:c6eabf748fc7a61dbb5a705c78bcf3d6377b1127a97d0ce965c11c44ba46896f\";\n\nconst whole = (amount: Quantity, what: string): number => {\n const seconds = valueIn(amount, \"s\");\n if (!Number.isInteger(seconds) || seconds < 0) throw new RangeError(`${what} is ${seconds} s, not a whole number of seconds`);\n return seconds;\n};\n\n/** This function gives the namespace of a cell: `lab-` and 10 digits of the sha256 of its name. */\nexport const namespaceOf = (cell: Pick<KindCell, \"name\">): string =>\n `lab-${createHash(\"sha256\").update(cell.name).digest(\"hex\").slice(0, 10)}`;\n\n/** These are the labels of the namespace of each mode of a mesh. */\nconst MESH_LABELS: Readonly<Record<MeshMode, Readonly<Record<string, string>>>> = {\n sidecar: { \"istio-injection\": \"enabled\" },\n ambient: { \"istio.io/dataplane-mode\": \"ambient\" },\n};\n\n/**\n * This function gives the Namespace object of a cell. In the sidecar mode of a mesh, the label\n * `istio-injection=enabled` makes the injector of Istio put a sidecar in each pod of the namespace.\n * In the ambient mode, the label `istio.io/dataplane-mode=ambient` makes istio-cni redirect the\n * traffic of each pod to ztunnel. The listeners of ztunnel are in the network namespace of the pod.\n */\nexport const namespaceObject = (cell: Pick<KindCell, \"mesh\">, namespace: string): Record<string, unknown> => ({\n apiVersion: \"v1\",\n kind: \"Namespace\",\n metadata: { name: namespace, ...(cell.mesh === undefined ? {} : { labels: { ...MESH_LABELS[cell.mesh.mode] } }) },\n});\n\n/**\n * This function gives a probe of the pod, with each field of the cell. The probe of the service is an\n * HTTP GET of its health server (`path`). The probe of grpc-go (`path` null) is the gRPC probe of the\n * kubelet. It is a Check of the service \"\" on the port of the server, and the answer is SERVING until\n * GracefulStop. A gRPC probe can name another service (`service`): the field `service` of `GRPCAction`\n * (`staging/src/k8s.io/api/core/v1/types.go` at v1.37.0, line 2892).\n */\nexport const probeOf = (path: string | null, probe: KubeletProbe, service?: string): Record<string, unknown> => ({\n ...(path === null ? { grpc: service === undefined ? { port: PORT } : { port: PORT, service } } : { httpGet: { path, port: HEALTH_PORT } }),\n initialDelaySeconds: 0,\n periodSeconds: whole(probe.period, `the period of ${path ?? \"the gRPC probe\"}`),\n timeoutSeconds: whole(probe.timeout, `the timeout of ${path ?? \"the gRPC probe\"}`),\n failureThreshold: probe.threshold,\n successThreshold: 1,\n});\n\n/** This variable is the name of the pod, from the API: the service and grpc-go put it in their lines. */\nconst POD_ENV = { name: \"POD_NAME\", valueFrom: { fieldRef: { fieldPath: \"metadata.name\" } } };\n\n/** This function gives the environment of the app: its config as JSON, and the name of its pod. */\nconst appEnv = (cell: KindCell): Array<Record<string, unknown>> => [\n { name: \"CONFIG\", value: JSON.stringify({ name: SERVER, port: PORT, healthPort: HEALTH_PORT, declaration: cell.declaration }) },\n POD_ENV,\n];\n\n/** This file is the flag of the trigger `liveness-restart`, in the root of the server process (`run.ts`, the step `flag`). */\nconst FLAG_FILE = \"/tmp/lab-fail\";\n\n/**\n * This function gives the container of the server of a cell. The service runs `app.mjs` of the lab\n * image with its declaration. The server grpc-go runs from the image of the harness. Its lines name\n * the pod and stamp in milliseconds. Its replies name the pod in the header `x-pod`. Kubernetes\n * expands `$(POD_NAME)` in the command.\n *\n * The seam of `liveness-restart` in grpc-go is its service `liveness`, which the flag file fails\n * (`--fail-file`). Thus for that trigger the liveness probe is a Check of `liveness`. The readiness\n * probe stays a Check of \"\", which the flag file does not fail.\n */\nconst serverContainer = (cell: KindCell, version: number): Record<string, unknown> => {\n const env = { name: \"LAB_VERSION\", value: String(version) };\n if (stackOf(cell) === \"service\")\n return {\n command: [\"node\", \"app.mjs\"],\n env: [...appEnv(cell), env],\n ports: [{ containerPort: PORT }, { containerPort: HEALTH_PORT }],\n ...(cell.pod.startup === null ? {} : { startupProbe: probeOf(\"/startup\", cell.pod.startup) }),\n readinessProbe: probeOf(\"/readiness\", cell.pod.readiness),\n livenessProbe: probeOf(\"/liveness\", cell.pod.liveness),\n };\n const seam = cell.trigger.kind === \"liveness-restart\";\n return {\n command: [\"grpc-go-server\", \"--port\", String(PORT), \"--name\", \"$(POD_NAME)\", \"--clock\", \"ms\", ...(seam ? [\"--fail-file\", FLAG_FILE] : [])],\n env: [POD_ENV, env],\n ports: [{ containerPort: PORT }],\n ...(cell.pod.startup === null ? {} : { startupProbe: probeOf(null, cell.pod.startup) }),\n readinessProbe: probeOf(null, cell.pod.readiness),\n livenessProbe: seam ? probeOf(null, cell.pod.liveness, \"liveness\") : probeOf(null, cell.pod.liveness),\n };\n};\n\n/** These are the objects of Redis: one replica behind a Service, with no persistence. */\nexport const redisObjects = (namespace: string, image: string): unknown[] => [\n {\n apiVersion: \"apps/v1\",\n kind: \"Deployment\",\n metadata: { name: REDIS, namespace, labels: { app: REDIS } },\n spec: {\n replicas: 1,\n selector: { matchLabels: { app: REDIS } },\n template: {\n metadata: { labels: { app: REDIS } },\n spec: {\n terminationGracePeriodSeconds: 1,\n containers: [\n {\n name: REDIS,\n image,\n imagePullPolicy: \"Never\",\n command: [\"redis-server\", \"--save\", \"\", \"--appendonly\", \"no\"],\n ports: [{ containerPort: 6379 }],\n readinessProbe: { tcpSocket: { port: 6379 }, periodSeconds: 1, failureThreshold: 1, successThreshold: 1, timeoutSeconds: 1 },\n },\n ],\n },\n },\n },\n },\n {\n apiVersion: \"v1\",\n kind: \"Service\",\n metadata: { name: REDIS, namespace },\n spec: { selector: { app: REDIS }, ports: [{ port: 6379, targetPort: 6379 }] },\n },\n];\n\n/**\n * This function gives the preStop hook of a sleep, or nothing for no sleep. The hook is the native\n * sleep action of the kubelet. The API server refuses a sleep action that is longer than the grace\n * (`validateSleepAction`, `pkg/apis/core/validation/validation.go` at v1.37.0, line 3483). Thus a\n * longer sleep is a command in the container, `sleep` from `/bin/sh`, as public manifests write it.\n * The kubelet starts that command with no timeout (`pkg/kubelet/lifecycle/handlers.go`, line 81).\n */\nconst preStopOf = (preStop: number, grace: number): Record<string, unknown> => {\n if (preStop === 0) return {};\n if (preStop <= grace) return { lifecycle: { preStop: { sleep: { seconds: preStop } } } };\n return { lifecycle: { preStop: { exec: { command: [\"/bin/sh\", \"-c\", `sleep ${preStop}`] } } } };\n};\n\n/**\n * These are the objects of the server: a Service of type ClusterIP, and the Deployment with the\n * settings of the cell. The image is the lab image for the service, or the image of grpc-go.\n */\nexport const serverObjects = (cell: KindCell, namespace: string, image: string, version = 1): unknown[] => {\n const preStop = whole(cell.pod.preStop, \"the preStop sleep\");\n const grace = whole(cell.pod.grace, \"the grace period\");\n return [\n {\n apiVersion: \"v1\",\n kind: \"Service\",\n metadata: { name: SERVER, namespace },\n spec: { selector: { app: SERVER }, ports: [{ name: stackOf(cell) === \"grpc\" ? \"grpc\" : \"http\", port: 80, targetPort: PORT }] },\n },\n {\n apiVersion: \"apps/v1\",\n kind: \"Deployment\",\n metadata: { name: SERVER, namespace, labels: { app: SERVER } },\n spec: {\n replicas: cell.pod.replicas,\n selector: { matchLabels: { app: SERVER } },\n strategy: { type: \"RollingUpdate\", rollingUpdate: { maxSurge: \"25%\", maxUnavailable: \"25%\" } },\n minReadySeconds: 0,\n template: {\n metadata: { labels: { app: SERVER, [LOG_LABEL]: \"1\", \"lab.systems/version\": String(version) } },\n spec: {\n terminationGracePeriodSeconds: grace,\n enableServiceLinks: true,\n topologySpreadConstraints: [\n {\n maxSkew: 1,\n topologyKey: \"kubernetes.io/hostname\",\n whenUnsatisfiable: \"ScheduleAnyway\",\n labelSelector: { matchLabels: { app: SERVER } },\n },\n ],\n containers: [\n {\n name: \"app\",\n image,\n imagePullPolicy: \"Never\",\n ...serverContainer(cell, version),\n ...preStopOf(preStop, grace),\n },\n ],\n },\n },\n },\n },\n ];\n};\n\n/**\n * These are the objects of the clients: a DaemonSet with one client on each node, the control plane\n * too. The client of the service is `client.mjs`, and the client of grpc-go is `client-grpc.mjs`.\n * Each one is in the lab image, and each one writes the same lines.\n */\nexport const clientObjects = (cell: KindCell, namespace: string, image: string, target: string): unknown[] => [\n {\n apiVersion: \"apps/v1\",\n kind: \"DaemonSet\",\n metadata: { name: CLIENT, namespace, labels: { app: CLIENT } },\n spec: {\n selector: { matchLabels: { app: CLIENT } },\n template: {\n metadata: { labels: { app: CLIENT, [LOG_LABEL]: \"1\" } },\n spec: {\n terminationGracePeriodSeconds: 0,\n tolerations: [{ key: \"node-role.kubernetes.io/control-plane\", operator: \"Exists\", effect: \"NoSchedule\" }],\n containers: [\n {\n name: CLIENT,\n image,\n imagePullPolicy: \"Never\",\n command: [\"node\", stackOf(cell) === \"grpc\" ? \"client-grpc.mjs\" : \"client.mjs\"],\n env: [\n { name: \"TARGET\", value: target },\n { name: \"EVERY_MS\", value: String(valueIn(cell.workload.every, \"ms\")) },\n { name: \"TIMEOUT_MS\", value: String(valueIn(cell.workload.timeout, \"ms\")) },\n { name: \"POD_NAME\", valueFrom: { fieldRef: { fieldPath: \"metadata.name\" } } },\n { name: \"NODE_NAME\", valueFrom: { fieldRef: { fieldPath: \"spec.nodeName\" } } },\n ],\n },\n ],\n },\n },\n },\n },\n];\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { Poll, Stage, Timing } from \"@mark1russell7/service\";\nimport { failSpan } from \"@mark1russell7/service\";\nimport { bandOf, seconds, secondsOf } from \"./bands.ts\";\nimport type { Band, Declaration, KubeletProbe } from \"./cell.ts\";\n\n/**\n * This module is the arithmetic of the predictions. Each band is a range of times after a\n * reference event of the run. Each term comes from a declaration, from the pod settings, or from\n * the calibration of the propagation. No term comes from a default. The arithmetic operates in\n * seconds, and each result is a quantity.\n */\n\nconst s = (amount: Quantity): number => valueIn(amount, \"s\");\n/** This function rounds a time to the microsecond, thus a band has no float residue. */\nconst us = (value: number): number => Math.round(value * 1e6) / 1e6;\nconst band = (lo: number, hi: number): Band => bandOf(us(lo), us(hi));\n\n/**\n * A propagation is the time from a change of an endpoint to the rules of each node. A\n * calibration measures it for each kube-proxy mode and node count. Without a calibration, its upper\n * bound is `minSyncPeriod`: an idle kube-proxy syncs a change at once, or one `minSyncPeriod`\n * after its last sync.\n */\nexport interface Propagation extends Band {\n /** This text tells where the bound comes from: a calibration, or the documented `minSyncPeriod`. */\n readonly from: string;\n}\n\n/** This propagation is the documented bound, before a calibration: from zero to one `minSyncPeriod` of 1 s. */\nexport const DOCUMENTED: Propagation = { lo: seconds(0), hi: seconds(1), from: \"minSyncPeriod (1 s), not calibrated\" };\n\n/**\n * This function gives the time from a flip of the probed state to the decision of the kubelet. The\n * next probe comes within one period, then `threshold − 1` more periods follow. A probe of an\n * endpoint that does not answer fails at its timeout.\n */\nexport const kubeletBand = (probe: KubeletProbe, hangs: boolean): Band => {\n const late = hangs ? s(probe.timeout) : 0;\n return band((probe.threshold - 1) * s(probe.period) + late, probe.threshold * s(probe.period) + late);\n};\n\n/**\n * This function gives the time from a fault of a dependency to the trip of a readiness or liveness\n * latch. A dependency that refuses fails each poll at once. A silent dependency fails each poll at\n * the request timeout of its client, or at the timeout of the poll when that is shorter. The\n * watchdog of the client can close the connection sooner, and then each poll fails at once.\n */\nexport const latchBand = (\n timing: Timing,\n poll: Poll,\n failure: \"refuses\" | \"silent\",\n requestTimeout?: Quantity,\n): Band => {\n const error = failSpan(timing, poll, \"error\");\n if (failure === \"refuses\") return band(s(error.min), s(error.max));\n const wait = requestTimeout === undefined ? s(poll.timeout) : Math.min(s(requestTimeout), s(poll.timeout));\n const late = failSpan(timing, { ...poll, timeout: seconds(wait) }, \"timeout\");\n return band(s(error.min), s(late.max));\n};\n\n/** This function gives the time from a fault to the removal of the endpoints from the rules: the latch, then the kubelet, then the propagation. */\nexport const removalBand = (latch: Band, probe: KubeletProbe, propagation: Propagation): Band => {\n const kubelet = secondsOf(kubeletBand(probe, false));\n const trip = secondsOf(latch);\n const spread = secondsOf(propagation);\n return band(trip.lo + kubelet.lo + spread.lo, trip.hi + kubelet.hi + spread.hi);\n};\n\n/**\n * This function gives the time from a fault to the SIGTERM of a replica whose liveness fails with\n * its readiness. The time is the latch, then the liveness probes of the kubelet, then the preStop sleep.\n */\nexport const restartBand = (latch: Band, liveness: KubeletProbe, preStop: Quantity): Band => {\n const kubelet = secondsOf(kubeletBand(liveness, false));\n const trip = secondsOf(latch);\n return band(trip.lo + kubelet.lo + s(preStop), trip.hi + kubelet.hi + s(preStop));\n};\n\n/**\n * This function gives the time from the start of a stall of the event loop to the removal of the\n * replica from the rules. Each probe in the stall fails at its timeout, because the loop does not answer it.\n */\nexport const stallRemovalBand = (readiness: KubeletProbe, propagation: Propagation): Band => {\n const kubelet = secondsOf(kubeletBand(readiness, true));\n const spread = secondsOf(propagation);\n return band(kubelet.lo + spread.lo, kubelet.hi + spread.hi);\n};\n\n/** This function gives the cap of a stage in a declaration, or zero when the declaration does not name the stage. */\nexport const capOf = (declaration: Declaration, stage: Stage): Quantity => {\n const found = declaration.phases.find(([name]) => name === stage);\n return seconds(found === undefined ? 0 : s(found[1]));\n};\n\n/**\n * This function gives the shortest time from the deletion of a pod to the stop of its intake. It\n * is the preStop sleep, then the hold of `fail readiness`. The kubelet adds its own delay before\n * the preStop, thus the real time is longer.\n */\nexport const intakeAfter = (declaration: Declaration, preStop: Quantity): Quantity =>\n seconds(us(s(preStop) + s(capOf(declaration, \"fail readiness\"))));\n\n/**\n * A deletion window is what a deletion predicts on one node. The listener of the pod closes at the\n * stop of its intake. The rules of the node stop the new connections to the pod at the\n * propagation. A connection between the two is refused. Thus the window is empty when the intake\n * stops after the latest propagation.\n */\nexport interface DeletionWindow {\n /** This flag tells that no connection can meet a closed listener. */\n readonly empty: boolean;\n /** This band is the end of the window after the deletion: the propagation. */\n readonly end: Band;\n /** This time is the earliest stop of the intake after the deletion. */\n readonly intake: Quantity;\n}\n\n/** This function gives the deletion window of a declaration and a preStop sleep on a node with a propagation. */\nexport const deletionWindow = (declaration: Declaration, preStop: Quantity, propagation: Propagation): DeletionWindow => {\n const intake = intakeAfter(declaration, preStop);\n const spread = secondsOf(propagation);\n return { empty: s(intake) >= spread.hi, end: band(spread.lo, spread.hi), intake };\n};\n\n/**\n * This function gives the window on a node whose kube-proxy pauses across a deletion. The pause\n * starts `lead` before the deletion and holds for `hold`. The node then syncs in one propagation\n * after the end of the pause, at `resume` after the deletion. The window is empty when the intake\n * stops after that.\n */\nexport const pausedWindow = (\n declaration: Declaration,\n preStop: Quantity,\n lead: Quantity,\n hold: Quantity,\n propagation: Propagation,\n): DeletionWindow & { readonly resume: Quantity } => {\n const intake = intakeAfter(declaration, preStop);\n const resume = us(s(hold) - s(lead));\n const spread = secondsOf(propagation);\n return { empty: s(intake) >= resume + spread.hi, end: band(spread.lo, spread.hi), intake, resume: seconds(resume) };\n};\n\nexport { showBand } from \"./bands.ts\";\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { Finding, Rule, Scenario, ServiceDescription, Verdict } from \"@mark1russell7/service\";\nimport { lint } from \"@mark1russell7/service\";\nimport { redisClient } from \"@mark1russell7/service/redis\";\nimport type { KindCell } from \"./cell.ts\";\nimport { compose } from \"./compose.ts\";\nimport type { Propagation } from \"./predict.ts\";\n\n/**\n * These are the rules of the ordering law that a drain in Kubernetes meets, as data. The caller of\n * `lint()` gives the rules, thus they are here and not in the service.\n */\n\n/**\n * O04 with the hold of the service. The listener of a pod stays open through the preStop sleep and\n * then through the hold of `fail readiness`. Thus the sum of the two must cover the propagation of\n * the endpoints. The rule of the research is the case without a hold.\n */\nexport const O04: Rule = {\n id: \"O04\",\n rule: \"the preStop sleep plus the hold of fail readiness is at least the endpoint propagation\",\n window: \"new connections reach a pod whose listener is closed\",\n expr: {\n lhs: {\n terms: [\n { knob: \"prestop_sleep\", coef: 1, at: \"pod\" },\n { knob: \"readiness_hold\", coef: 1, at: \"process\" },\n ],\n },\n op: \">=\",\n rhs: { terms: [{ knob: \"propagation_bound\", coef: 1, at: \"cluster\" }] },\n },\n};\n\n/** O09 is W8: readiness detection plus propagation is no longer than liveness detection plus the preStop sleep. */\nexport const O09: Rule = {\n id: \"O09\",\n rule: \"readiness detection plus endpoint propagation is no longer than liveness detection plus the preStop sleep\",\n window: \"traffic reaches a container that its liveness probe kills\",\n expr: {\n lhs: {\n terms: [\n { knob: \"readiness_detection_window\", coef: 1, at: \"pod\" },\n { knob: \"propagation_bound\", coef: 1, at: \"cluster\" },\n ],\n },\n op: \"<=\",\n rhs: {\n terms: [\n { knob: \"liveness_detection_window\", coef: 1, at: \"pod\" },\n { knob: \"prestop_sleep\", coef: 1, at: \"pod\" },\n ],\n },\n },\n};\n\n/** O10: the preStop sleep plus the drain of the process fits in the grace period. */\nexport const O10: Rule = {\n id: \"O10\",\n rule: \"the preStop sleep plus the shutdown of the process fits in the grace period\",\n window: \"work in flight at SIGKILL is cut, and each connection closes at once\",\n expr: {\n lhs: {\n terms: [\n { knob: \"prestop_sleep\", coef: 1, at: \"pod\" },\n { knob: \"process_drain_time\", coef: 1, at: \"process\" },\n ],\n },\n op: \"<=\",\n rhs: { terms: [{ knob: \"termination_grace\", coef: 1, at: \"pod\" }] },\n },\n};\n\n/** O16: the cooperative window is shorter than the drain, which is shorter than the grace less the preStop sleep. */\nexport const O16: Rule = {\n id: \"O16\",\n rule: \"the cooperative window is shorter than the drain, which is shorter than the grace less the preStop sleep\",\n window: \"handlers are cut before they answer, or the drain itself gets SIGKILL\",\n expr: {\n lhs: { terms: [{ knob: \"cooperative_window\", coef: 1, at: \"process\" }] },\n op: \"<\",\n rhs: { terms: [{ knob: \"process_drain_time\", coef: 1, at: \"process\" }] },\n },\n expr_more: [\n {\n lhs: { terms: [{ knob: \"process_drain_time\", coef: 1, at: \"process\" }] },\n op: \"<\",\n rhs: {\n terms: [\n { knob: \"termination_grace\", coef: 1, at: \"pod\" },\n { knob: \"prestop_sleep\", coef: -1, at: \"pod\" },\n ],\n },\n },\n ],\n};\n\n/** These are the rules of the drain that each kind cell gets. */\nexport const DRAIN_RULES: readonly Rule[] = [O04, O09, O10, O16];\n\nconst seconds = (amount: Quantity): number => valueIn(amount, \"s\");\nconst s = (value: number): Quantity => ({ value, unit: \"s\" });\n\n/**\n * This function gives the scenario of a cell for `lint()`: the preStop sleep, the grace, the probe\n * period and the propagation. The probe windows are the period times the threshold of each probe.\n */\nexport const scenarioOf = (cell: KindCell, propagation: Propagation): Scenario => ({\n platform: {\n \"prestop_sleep@pod\": cell.pod.preStop,\n \"termination_grace@pod\": cell.pod.grace,\n \"probe_period@pod\": cell.pod.readiness.period,\n \"propagation_bound@cluster\": propagation.hi,\n },\n probes: {\n readiness: s(seconds(cell.pod.readiness.period) * cell.pod.readiness.threshold),\n liveness: s(seconds(cell.pod.liveness.period) * cell.pod.liveness.threshold),\n },\n});\n\n/** A verdict set is the verdict of each drain rule for one declaration, by the identifier of the rule. */\nexport type Verdicts = Readonly<Record<string, Verdict>>;\n\n/** This function applies the drain rules to the description of a cell, and gives each finding. */\nexport const lintCell = (description: ServiceDescription, cell: KindCell, propagation: Propagation): Finding[] =>\n lint(description, scenarioOf(cell, propagation), DRAIN_RULES);\n\n/** This function gives the verdict of each drain rule, by its identifier. */\nexport const verdictsOf = (findings: readonly Finding[]): Verdicts =>\n Object.fromEntries(findings.map((finding) => [finding.rule, finding.verdict]));\n\n/** This function gives the description of the service of a cell, as the app of the image composes it. Nothing connects. */\nexport const describeCell = (cell: KindCell): ServiceDescription => {\n if (cell.declaration === null) throw new Error(`${cell.name}: the server is not the service, thus the cell has no declaration to describe`);\n const redis = cell.declaration.redis;\n const client =\n redis === null\n ? undefined\n : redisClient(\"redis\", { host: \"redis\", port: 6379, system: \"systems.component.redis\", knobs: redis.knobs, heartbeatMisses: redis.misses });\n return compose(\"lab\", cell.declaration, { redis: client }).describe();\n};\n","import { mkdirSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { StartedTestContainer } from \"testcontainers\";\nimport { imageOf } from \"@mark1russell7/harness\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { KindCell, KindRun, Replica } from \"./cell.ts\";\nimport type { Cluster } from \"./cluster.ts\";\nimport { command, createCluster } from \"./cluster.ts\";\nimport type { Step, Target } from \"./faults.ts\";\nimport { NODE, POD, VERSION, deletes as deletesAPod, planOf } from \"./faults.ts\";\nimport { labImage } from \"./image.ts\";\nimport type { MeshImages } from \"./istio.ts\";\nimport { EDS_EVERY, ISTIO_IMAGES, LDS_EVERY, MESH_IMAGES, followsZtunnel, installMesh, meshKey, pollsListeners, serverCluster, setZtunnelEnv } from \"./istio.ts\";\nimport type { KindLab, Line, Stamp } from \"./lab.ts\";\nimport { localPin, openKindLab } from \"./lab.ts\";\nimport { CLIENT, REDIS, REDIS_IMAGE, SERVER, clientObjects, namespaceObject, namespaceOf, redisObjects, serverObjects, stackOf } from \"./manifests.ts\";\nimport type { PodLine, Request } from \"./observe.ts\";\nimport { byClients, ztunnelReplaced } from \"./observe.ts\";\nimport type { EnvironmentManifest, KindCellRecord, KindCellResult, LoadSample } from \"./record.ts\";\nimport { FORMAT } from \"./record.ts\";\nimport { scenario } from \"./scenario.ts\";\nimport { judgeKind, observationAfter, observationEnd } from \"./verdict.ts\";\n\n/** This time is the interval of the poll of the rules on each node. */\nexport const RULES_EVERY: Quantity = { value: 50, unit: \"ms\" };\n\n/** These options run a catalog of kind cells. */\nexport interface RunOptions {\n /** This folder gets the raw record of each cell and `summary.ndjson`. */\n readonly out: string;\n readonly log: (line: string) => void;\n /** This count is the number of environments, each a new cluster. */\n readonly environments?: number;\n /** This share of the CPUs of the VM must be idle before a run starts. */\n readonly idle?: number;\n /**\n * This folder holds the run folder of each cluster: its config, its kubeconfig and the cache of\n * kubectl. It is not the output, because a kubeconfig holds credentials.\n */\n readonly work?: string;\n}\n\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\nconst slug = (name: string): string => name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n/** This function tells if a cell has Redis: only a declaration of the service can have it. */\nconst hasRedis = (cell: KindCell): boolean => cell.declaration !== null && cell.declaration.redis !== null;\n\n/** These are the images of a cluster: the lab, Redis, grpc-go for the stack `grpc`, and Istio for a mesh. */\ninterface Images {\n readonly lab: string;\n readonly redis: string;\n readonly grpc?: string | undefined;\n readonly mesh?: MeshImages | undefined;\n}\n\n/** This function gives the key of the cluster of a cell: its kube-proxy mode, its node count, and the flavor of its mesh. */\nconst clusterKey = (cell: KindCell): string => (cell.mesh === undefined ? `${cell.mode}-${cell.workers}` : `${cell.mode}-${cell.workers}-${cell.mesh.flavor}`);\n\n/**\n * This tracker keeps the latest state of the cell from the lines: each pod, each slice and each\n * client. It also has, for each node, the backends of its rules and the hosts of its client sidecar.\n */\nclass Tracker {\n readonly pods = new Map<string, PodLine>();\n readonly slices = new Map<string, Line>();\n readonly backends = new Map<string, readonly string[]>();\n readonly hosts = new Map<string, readonly string[]>();\n readonly clients = new Map<string, { node: string; ok: number; failed: number }>();\n /** This map is the backend of the last answer of each client, by client. */\n readonly answers = new Map<string, string>();\n last = 0;\n see = (line: Line): void => {\n if (typeof line.t === \"number\" && line.t > this.last && line.ev === \"req\") this.last = line.t;\n if (line.ev === \"pod\") {\n const pod = line as unknown as PodLine;\n if (pod.type === \"DELETED\") this.pods.delete(pod.name);\n else this.pods.set(pod.name, pod);\n } else if (line.ev === \"slice\") {\n if (line[\"type\"] === \"DELETED\") this.slices.delete(String(line[\"name\"]));\n else this.slices.set(String(line[\"name\"]), line);\n } else if (line.ev === \"rules\") {\n this.backends.set(String(line[\"node\"]), line[\"b\"] as readonly string[]);\n } else if (line.ev === \"eds\") {\n this.hosts.set(String(line[\"node\"]), line[\"b\"] as readonly string[]);\n } else if (line.ev === \"req\") {\n const request = line as unknown as Request;\n const client = this.clients.get(request.p) ?? { node: request.n, ok: 0, failed: 0 };\n if (request.o === \"ok\") client.ok = Math.max(client.ok, request.t);\n else client.failed = Math.max(client.failed, request.t);\n this.clients.set(request.p, client);\n if (request.o === \"ok\" && request.b !== undefined) this.answers.set(request.p, request.b);\n }\n };\n servers(): PodLine[] {\n return [...this.pods.values()].filter((pod) => pod.labels[\"app\"] === SERVER).sort((a, b) => a.name.localeCompare(b.name));\n }\n}\n\n/** A context is the state of one cell in one environment. */\ninterface Context {\n readonly lab: KindLab;\n readonly cluster: Cluster;\n readonly cell: KindCell;\n readonly namespace: string;\n readonly tracker: Tracker;\n readonly clusterIp: string;\n readonly loads: LoadSample[];\n readonly loader: StartedTestContainer;\n readonly idle: number;\n readonly log: (line: string) => void;\n version: number;\n probed: string;\n /** This key is the server pods whose sidecars the LDS pollers read, or \"\" for none. */\n listened: string;\n /** This count is the replicas of the service at rest: one less while a scale-in holds. */\n expected: number;\n /** This map gives the PID of the app of each pod. A replica gets it from the last fault of the target `servers`, and a client from the last gap. */\n readonly pids: Map<string, number>;\n}\n\n/** This function tells why the cell is not at rest, or gives null when it is. */\nconst unsteady = (ctx: Context): string | null => {\n const { tracker, cell } = ctx;\n const servers = tracker.servers();\n if (servers.length !== ctx.expected) return `${servers.length} replicas, not ${ctx.expected}`;\n const busy = servers.find((pod) => pod.del || !pod.ready);\n if (busy !== undefined) return `${busy.name} is not ready`;\n if (new Set(servers.map((pod) => pod.labels[\"pod-template-hash\"])).size > 1) return \"two versions of the replicas\";\n if (hasRedis(cell)) {\n const redis = [...tracker.pods.values()].filter((pod) => pod.labels[\"app\"] === REDIS);\n if (redis.length !== 1 || !redis[0]!.ready || redis[0]!.del) return \"Redis is not ready\";\n }\n const ips = servers.map((pod) => pod.ip ?? \"\").sort();\n const ready = [...tracker.slices.values()]\n .filter((slice) => slice[\"svc\"] === SERVER)\n .flatMap((slice) => (slice[\"eps\"] as Array<{ ip: string; r: boolean }>).filter((endpoint) => endpoint.r).map((endpoint) => endpoint.ip))\n .sort();\n if (ready.join() !== ips.join()) return `the EndpointSlice has ${ready.join(\" \")}, not ${ips.join(\" \")}`;\n const expected = ips.map((ip) => `${ip}:8080`).sort().join();\n for (const node of ctx.cluster.nodes)\n if ((tracker.backends.get(node) ?? []).join() !== expected) return `the rules of ${node} have ${(tracker.backends.get(node) ?? []).join(\" \")}`;\n // in a sidecar mesh, the client sidecar of each node picks the backend of each call from its endpoints\n if (cell.mesh?.mode === \"sidecar\")\n for (const node of ctx.cluster.nodes)\n if ((tracker.hosts.get(node) ?? []).join() !== expected) return `the client sidecar of ${node} has ${(tracker.hosts.get(node) ?? []).join(\" \") || \"no host\"}`;\n const nodes = new Set([...tracker.clients.values()].map((client) => client.node));\n if (nodes.size !== ctx.cluster.nodes.length) return `clients answer on ${nodes.size} nodes`;\n for (const [pod, client] of tracker.clients) {\n if (tracker.last - client.ok > 1000) return `client ${pod} has no answer in the last second`;\n if (tracker.last - client.failed < 2000) return `client ${pod} failed in the last 2 s`;\n }\n return null;\n};\n\n/** This function waits until the cell is at rest, and gives null, or the reason when it is not at rest in time. */\nconst steady = async (ctx: Context, within: number): Promise<string | null> => {\n const end = Date.now() + within;\n let why = unsteady(ctx);\n while (why !== null && Date.now() < end) {\n await sleep(200);\n why = unsteady(ctx);\n }\n return why;\n};\n\n/** This function samples the load of the VM for 4 s, and names the lab containers that are not of this run. */\nconst sample = async (ctx: Context, ours: ReadonlySet<string>): Promise<LoadSample> => {\n const result = await ctx.loader.exec([\"top\", \"-b\", \"-n\", \"2\", \"-d\", \"4\"]);\n const second = result.stdout.split(/^Mem:/m).at(-1) ?? \"\";\n const idle = Number(/(\\d+)% idle/.exec(second)?.[1] ?? \"0\") / 100;\n const top = second\n .split(\"\\n\")\n .filter((line) => /^\\s*\\d+\\s/.test(line))\n .slice(0, 6)\n .map((line) => line.trim().replace(/\\s+/g, \" \"));\n const listed = (await command(\"docker\", [\"ps\", \"--format\", \"{{.ID}} {{.Names}} {{.Image}}\"])).trim().split(\"\\n\");\n const foreign = listed\n .filter((line) => line !== \"\")\n .filter((line) => !ours.has(line.split(\" \")[1]!) && !ours.has(line.split(\" \")[0]!))\n .filter((line) => /loom-|kindest\\/node|toxiproxy|haproxy|redis|systems-kind-lab|f6-/.test(line.split(\" \")[2] ?? \"\"));\n return { t: Date.now(), idle, top, foreign };\n};\n\n/** This function waits until the VM is quiet: no lab container of another agent, and an idle share of the CPUs. */\nconst quiet = async (ctx: Context, ours: ReadonlySet<string>): Promise<LoadSample> => {\n for (;;) {\n // the prober and the watcher start again with new names, thus the names of this lab come at each sample\n const load = await sample(ctx, new Set([...ours, ...ctx.lab.own(), ctx.loader.getName().replace(/^\\//, \"\")]));\n ctx.loads.push(load);\n if (load.foreign.length === 0 && load.idle >= ctx.idle) return load;\n ctx.log(` waiting: ${Math.round(load.idle * 100)}% idle, other lab containers: ${load.foreign.join(\"; \") || \"none\"}`);\n await sleep(30_000);\n }\n};\n\n/**\n * This function deploys a cell in its namespace, with the watcher, the poll of the rules and the\n * clients. A client of gRPC starts after the server is ready: each failed connect of a channel makes\n * the backoff of grpc-js longer, up to 120 s.\n */\nconst deploy = async (lab: KindLab, cluster: Cluster, cell: KindCell, namespace: string, images: Images): Promise<string> => {\n await cluster.apply([namespaceObject(cell, namespace)]);\n await lab.watch([namespace], { ztunnel: followsZtunnel(cell) });\n if (hasRedis(cell)) {\n await cluster.apply(redisObjects(namespace, images.redis));\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"status\", `deployment/${REDIS}`, \"--timeout=180s\"]);\n }\n const grpc = stackOf(cell) === \"grpc\";\n if (grpc && images.grpc === undefined) throw new Error(`${cell.name}: the cluster has no image of grpc-go`);\n await cluster.apply(serverObjects(cell, namespace, grpc ? images.grpc! : lab.image));\n const clusterIp = (await cluster.kubectl([\"-n\", namespace, \"get\", \"service\", SERVER, \"-o\", \"jsonpath={.spec.clusterIP}\"])).trim();\n const clients = clientObjects(cell, namespace, lab.image, `${clusterIp}:80`);\n if (!grpc) await cluster.apply(clients);\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"status\", `deployment/${SERVER}`, \"--timeout=300s\"]);\n if (grpc) await cluster.apply(clients);\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"status\", `daemonset/${CLIENT}`, \"--timeout=300s\"]);\n await lab.rules({ mode: cluster.scenario.kubeProxy.mode, ns: namespace, svc: SERVER, ip: clusterIp, port: 80, every: RULES_EVERY });\n if (cell.mesh?.mode === \"sidecar\") await pollEndpoints(lab, cluster, namespace);\n return clusterIp;\n};\n\n/**\n * This function starts the EDS poll of the client sidecar on each node: the hosts of the server\n * cluster, each 50 ms. The agent enters the network namespace of the client pod through the PID of\n * its client process. The sidecar shares that namespace.\n */\nconst pollEndpoints = async (lab: KindLab, cluster: Cluster, namespace: string): Promise<void> => {\n const listed = JSON.parse(await cluster.kubectl([\"-n\", namespace, \"get\", \"pods\", \"-l\", `app=${CLIENT}`, \"-o\", \"json\"])) as {\n items: Array<{ metadata: { name: string; deletionTimestamp?: string }; spec: { nodeName?: string } }>;\n };\n const pods: Array<{ node: string; pod: string; pid: number }> = [];\n for (const item of listed.items.filter((each) => each.metadata.deletionTimestamp === undefined)) {\n const node = item.spec.nodeName ?? \"\";\n pods.push({ node, pod: item.metadata.name, pid: await lab.pidOf(node, namespace, item.metadata.name, CLIENT) });\n }\n await lab.eds({ cluster: serverCluster(namespace), every: EDS_EVERY, pods });\n};\n\n/**\n * This function starts the LDS poll of the sidecar of each server pod, each 50 ms, for a cell that\n * polls them (`pollsListeners`). It starts the pollers again only when the server pods changed. The\n * agent enters the network namespace of the pod through the PID of its app.\n */\nconst pollListeners = async (ctx: Context): Promise<void> => {\n if (!pollsListeners(ctx.cell)) return;\n const servers = ctx.tracker.servers().filter((pod) => !pod.del && pod.node !== null);\n const key = servers.map((pod) => `${pod.name}/${pod.cs.map((status) => status.id).join(\"+\")}`).sort().join();\n if (key === ctx.listened) return;\n const pods: Array<{ node: string; pod: string; pid: number }> = [];\n for (const pod of servers) pods.push({ node: pod.node!, pod: pod.name, pid: await ctx.lab.pidOf(pod.node!, ctx.namespace, pod.name, \"app\") });\n await ctx.lab.lds({ every: LDS_EVERY, pods });\n ctx.listened = key;\n};\n\n/** This function starts the prober again when the pods of the cell changed: it watches the namespace of each pod. */\nconst probeThePods = async (ctx: Context): Promise<void> => {\n const pods = [...ctx.tracker.pods.values()].filter((pod) => !pod.del && pod.node !== null && pod.cs.every((status) => status.st === \"running\"));\n const key = pods.map((pod) => `${pod.name}/${pod.cs.map((status) => status.id).join(\"+\")}`).sort().join();\n if (key === ctx.probed) return;\n const netns: number[] = [];\n for (const pod of pods) {\n const container = pod.labels[\"app\"] === SERVER ? \"app\" : pod.labels[\"app\"] === CLIENT ? CLIENT : REDIS;\n const pid = await ctx.lab.pidOf(pod.node!, ctx.namespace, pod.name, container);\n netns.push(await ctx.lab.netnsOf(pod.node!, pid));\n }\n await ctx.lab.probe([...new Set(netns)]);\n ctx.probed = key;\n};\n\n/**\n * This function gives the replicas of the server at this time, in the sequence of their names. The\n * first one is the target of a run. A client of gRPC holds its connection to one replica, and the\n * channels go to the replica that stays after a run. Thus for the stack `grpc`, the first replica\n * is the one that answers the most clients (`byClients`).\n */\nconst replicasOf = (ctx: Context): Replica[] => {\n const replicas = ctx.tracker.servers().map((pod) => ({ pod: pod.name, node: pod.node ?? \"\", ip: pod.ip ?? \"\", version: pod.labels[\"pod-template-hash\"] ?? \"\" }));\n return stackOf(ctx.cell) === \"grpc\" ? byClients(replicas, ctx.tracker.answers) : replicas;\n};\n\n/**\n * This time is how long before the commit of its rules the lab starts the injector in each replica.\n * It is more than the execs of one node before its commit: a read of its tables and the injector.\n */\nconst INJECT_LEAD_MS = 700;\n\n/**\n * This function puts the rules of an action in the network namespace of each replica, with each\n * client pod as a peer: the target `servers`. The rules of all replicas commit at one instant on\n * CLOCK_MONOTONIC (`--at`). Thus no client meets one replica with the rule and one without it. The\n * replicas of one node go one after the other, and the nodes at the same time.\n *\n * The fault finds the PID of each replica, and the clear uses it again: thus the heal comes soon\n * after its hold. Before the commit of a clear, the lab reads the filter table of each replica. Its\n * counters show if the rule matched the clients beside the rules of istio-cni. After the commit of a\n * fault, the lab reads the nat table, with the redirect of istio-cni. The stamp is the first commit\n * and the last end.\n */\nconst injectServers = async (ctx: Context, action: string, replicas: readonly Replica[]): Promise<Stamp> => {\n const clients = [...ctx.tracker.pods.values()]\n .filter((pod) => pod.labels[\"app\"] === CLIENT && !pod.del && pod.ip !== null)\n .map((pod) => pod.ip!)\n .sort();\n if (clients.length === 0 || replicas.length === 0) throw new Error(`the injector needs clients and replicas: ${clients.length} clients, ${replicas.length} replicas`);\n const clear = action.startsWith(\"clear\");\n const byNode = new Map<string, number[]>();\n for (const replica of replicas) {\n const known = clear ? ctx.pids.get(replica.pod) : undefined;\n const pid = known ?? (await ctx.lab.pidOf(replica.node, ctx.namespace, replica.pod, \"app\"));\n ctx.pids.set(replica.pod, pid);\n byNode.set(replica.node, [...(byNode.get(replica.node) ?? []), pid]);\n }\n const at = (await ctx.lab.now()) + INJECT_LEAD_MS;\n const args = [action, clients.join(\",\"), \"--at\", String(Math.round(at * 1e6))];\n const stamps = await Promise.all(\n [...byNode].map(async ([node, pids]) => {\n const out: Stamp[] = [];\n for (const pid of pids) {\n if (clear) await ctx.lab.tables(node, pid, \"filter\");\n out.push(await ctx.lab.inject(node, pid, args));\n }\n return out;\n }),\n );\n if (!clear) for (const [node, pids] of byNode) for (const pid of pids) await ctx.lab.tables(node, pid, \"nat\");\n const all = stamps.flat();\n return { t0: Math.min(...all.map((stamp) => stamp.t0)), t1: Math.max(...all.map((stamp) => stamp.t1)) };\n};\n\n/**\n * This function sends a signal to the process of each client pod: the target `clients`. The nodes go\n * at the same time. SIGUSR1 finds the PID of each client, and SIGUSR2 uses it again, thus the heal of\n * a gap comes soon after its hold. When that PID fails, the client started again, and the lab finds\n * its PID. The stamp is the first act and the last end.\n */\nconst signalClients = async (ctx: Context, signal: string): Promise<Stamp> => {\n const clients = [...ctx.tracker.pods.values()].filter((pod) => pod.labels[\"app\"] === CLIENT && !pod.del && pod.node !== null);\n if (clients.length === 0) throw new Error(`the signal ${signal} needs clients`);\n const stamps = await Promise.all(\n clients.map(async (pod) => {\n const node = pod.node!;\n const known = signal === \"SIGUSR2\" ? ctx.pids.get(pod.name) : undefined;\n if (known !== undefined) {\n try {\n return await ctx.lab.act(node, { op: \"signal\", pid: known, signal });\n } catch {\n // the client started again: its PID is new\n }\n }\n const pid = await ctx.lab.pidOf(node, ctx.namespace, pod.name, CLIENT);\n ctx.pids.set(pod.name, pid);\n return ctx.lab.act(node, { op: \"signal\", pid, signal });\n }),\n );\n return { t0: Math.min(...stamps.map((stamp) => stamp.t0)), t1: Math.max(...stamps.map((stamp) => stamp.t1)) };\n};\n\n/** This function does one step of a fault plan. It gives the stamp of its act, or null for an act without a stamp. */\nconst doStep = async (ctx: Context, step: Step, replicas: readonly Replica[], background: Array<Promise<unknown>>): Promise<Stamp | null> => {\n const { lab, cluster, namespace } = ctx;\n const target = replicas[0]!;\n const fill = (args: readonly string[]): string[] =>\n args.map((arg) =>\n arg.replaceAll(\"{namespace}\", namespace).replaceAll(POD, target.pod).replaceAll(NODE, target.node).replaceAll(VERSION, String(ctx.version)),\n );\n const where = async (which: Target): Promise<{ node: string; pid: number; pod: PodLine | null }> => {\n if (which === \"servers\") throw new Error(\"the target servers is a target of the injector only\");\n if (which === \"clients\") throw new Error(\"the target clients is a target of a signal only\");\n if (which === \"server\") return { node: target.node, pid: await lab.pidOf(target.node, namespace, target.pod, \"app\"), pod: null };\n if (which === \"redis\") {\n const redis = [...ctx.tracker.pods.values()].find((pod) => pod.labels[\"app\"] === REDIS && !pod.del)!;\n return { node: redis.node!, pid: await lab.pidOf(redis.node!, namespace, redis.name, REDIS), pod: redis };\n }\n const worker = cluster.nodes[1] ?? cluster.nodes[0]!;\n const proxy = JSON.parse(\n await cluster.kubectl([\"-n\", \"kube-system\", \"get\", \"pods\", \"-l\", \"k8s-app=kube-proxy\", \"--field-selector\", `spec.nodeName=${worker}`, \"-o\", \"json\"]),\n ) as { items: Array<{ metadata: { name: string } }> };\n return { node: worker, pid: await lab.pidOf(worker, \"kube-system\", proxy.items[0]!.metadata.name, \"kube-proxy\"), pod: null };\n };\n switch (step.do) {\n case \"kubectl\": {\n const t0 = await lab.now();\n if (step.args.includes(`LAB_VERSION=${VERSION}`)) ctx.version += 1;\n const call = cluster.kubectl(fill(step.args));\n if (step.background === true) {\n background.push(call.catch((error: unknown) => ctx.log(` kubectl ${step.args.join(\" \")}: ${String(error)}`)));\n return { t0, t1: t0 };\n }\n await call;\n return { t0, t1: await lab.now() };\n }\n case \"signal\": {\n if (step.target === \"clients\") return signalClients(ctx, step.signal);\n const at = await where(step.target);\n return lab.act(at.node, { op: \"signal\", pid: at.pid, signal: step.signal });\n }\n case \"pause\": {\n const at = await where(step.target);\n const stamp = lab.act(at.node, { op: \"pause\", pid: at.pid, ms: valueIn(step.time, \"ms\") });\n background.push(stamp);\n const now = await lab.now();\n return { t0: now, t1: now };\n }\n case \"inject\": {\n if (step.target === \"servers\") return injectServers(ctx, step.action, replicas);\n const at = await where(step.target);\n let first: Stamp | null = null;\n for (const replica of replicas) {\n const stamp = await lab.inject(at.node, at.pid, [step.action, replica.ip]);\n first ??= stamp;\n }\n return first;\n }\n case \"flag\": {\n const at = await where(step.target);\n return step.on\n ? lab.act(at.node, { op: \"write\", path: `/proc/${at.pid}/root/tmp/lab-fail`, data: \"1\" })\n : lab.act(at.node, { op: \"remove\", path: `/proc/${at.pid}/root/tmp/lab-fail` });\n }\n case \"cpu\": {\n const at = await where(step.target);\n const cgroup = await lab.cgroupOf(at.node, at.pid);\n return lab.act(at.node, { op: \"write\", path: `${cgroup}/cpu.max`, data: step.max });\n }\n case \"stall\": {\n const at = await where(step.target);\n return lab.act(at.node, { op: \"stall\", pid: at.pid, ms: valueIn(step.time, \"ms\") });\n }\n case \"wait\":\n await sleep(valueIn(step.time, \"ms\"));\n return null;\n case \"apply\": {\n // the marks are in the strings of the object: its JSON keeps them as they are\n const object = JSON.parse(fill([JSON.stringify(step.object)])[0]!) as Record<string, unknown>;\n const t0 = await lab.now();\n await cluster.apply([object]);\n return { t0, t1: await lab.now() };\n }\n }\n};\n\n/** This function tells if a run is over: the trigger is done, and the cell is at rest again. */\nconst over = (\n ctx: Context,\n run: { readonly target: Replica | null; readonly replicas: readonly Replica[]; readonly restarts: ReadonlyMap<string, number> },\n lines: readonly Line[],\n): boolean => {\n if (unsteady(ctx) !== null) return false;\n const kind = ctx.cell.trigger.kind;\n if ((kind === \"crash\" || kind === \"liveness-restart\") && run.target !== null) {\n const pod = ctx.tracker.pods.get(run.target.pod);\n const now = pod === undefined ? 0 : pod.cs.reduce((sum, status) => sum + status.rc, 0);\n return now > (run.restarts.get(run.target.pod) ?? 0);\n }\n if (run.target !== null && [\"pod-deletion\", \"node-drain\", \"scale-in\", \"freeze-process\"].includes(kind) && (ctx.cell.template === \"T07\" || ctx.cell.template === \"T15\"))\n return kind === \"scale-in\" || kind === \"node-drain\"\n ? run.replicas.some((replica) => !ctx.tracker.pods.has(replica.pod))\n : !ctx.tracker.pods.has(run.target.pod);\n if (kind === \"rolling-update\" || kind === \"rollout\") return run.replicas.every((replica) => !ctx.tracker.pods.has(replica.pod));\n if (kind === \"app-stall\") return lines.some((line) => line.ev === \"stall\" && line[\"phase\"] === \"end\");\n if (kind === \"ztunnel-restart\") return ztunnelReplaced(lines, ctx.cluster.nodes);\n return true;\n};\n\n/** This function gives the replica that the trigger stopped. For a scale-in or a drain, the controller chooses it. */\nconst stoppedOf = (cell: KindCell, replicas: readonly Replica[], target: Replica | null, lines: readonly Line[]): Replica | null =>\n deletesAPod(cell) ? (replicas.find((replica) => lines.some((line) => line.ev === \"pod\" && line[\"name\"] === replica.pod && line[\"del\"] === true)) ?? target) : target;\n\n/**\n * This function does one run of a cell: at rest, the trigger, the heal, then the observation. The\n * observation lasts until the cell is at rest again, and until each predicted window can close\n * (`observationEnd`).\n */\nconst runOnce = async (ctx: Context, env: number, index: number, warmup: boolean, ours: Set<string>): Promise<KindRun> => {\n const { lab, cell, cluster, namespace } = ctx;\n const plan = planOf(cell);\n const atRest = await steady(ctx, 300_000);\n const empty = (why: string, start: number): KindRun => ({\n env, index, warmup, start, end: start, acts: { fault: null, heal: null, resume: null }, target: null, paused: null, replicas: [], void: why, lines: lab.take(), dropped: {},\n });\n if (atRest !== null) return empty(`the cell was not at rest: ${atRest}`, await lab.now());\n await probeThePods(ctx);\n await pollListeners(ctx);\n await quiet(ctx, ours);\n if ((await steady(ctx, 300_000)) !== null) return empty(\"the cell was not at rest after the wait for a quiet VM\", await lab.now());\n const replicas = replicasOf(ctx);\n const restarts = new Map(ctx.tracker.servers().map((pod) => [pod.name, pod.cs.reduce((sum, status) => sum + status.rc, 0)]));\n const target = hasRedis(cell) && [\"rst\", \"blackhole\", \"freeze-process\", \"peer-gone-without-close\", \"rollout\"].includes(cell.trigger.kind) ? null : replicas[0]!;\n const paused = cell.template === \"T07\" && cell.trigger.kind === \"freeze-process\" ? (cluster.nodes[1] ?? cluster.nodes[0]!) : null;\n // A SYN with no answer before the run keeps its NAT for 120 s (SYN_SENT). A client that takes its\n // port again in the run meets it, and the restore of the last run leaves such SYNs. Each run starts with none\n const cleared = await lab.forgetHandshakes(ctx.clusterIp, 80);\n lab.take();\n for (const key of Object.keys(lab.dropped)) delete lab.dropped[key];\n const start = await lab.now();\n const background: Array<Promise<unknown>> = [];\n let fault: number | null = null;\n let resume: number | null = null;\n if (cell.trigger.kind === \"scale-in\") ctx.expected = cell.pod.replicas - 1;\n const notes: string[] = [];\n const forgotten = Object.entries(cleared).filter(([, count]) => count > 0);\n if (forgotten.length > 0) notes.push(`before the run, the lab deleted SYN_SENT entries of the ClusterIP: ${forgotten.map(([node, count]) => `${count} on ${node}`).join(\", \")}`);\n for (const step of plan.inject) {\n const stamp = await doStep(ctx, step, replicas, background);\n if (stamp !== null && fault === null) fault = stamp.t0;\n }\n let heal: number | null = null;\n if (plan.hold !== null) {\n const now = await lab.now();\n await sleep(Math.max(0, valueIn(plan.hold, \"ms\") - (now - (fault ?? now))));\n for (const step of plan.heal) {\n try {\n const stamp = await doStep(ctx, step, replicas, background);\n if (stamp !== null && heal === null) heal = stamp.t0;\n } catch (error) {\n notes.push(`the heal ${step.do} failed: ${String(error)}`);\n }\n }\n }\n const settled = await Promise.allSettled(background);\n for (const each of settled) {\n if (each.status === \"fulfilled\" && typeof each.value === \"object\" && each.value !== null && \"t1\" in each.value) resume = (each.value as Stamp).t1;\n }\n // the observation: at least 3 s after the last act, until the trigger happened and the cell is at rest again\n const limit = Date.now() + 240_000 + valueIn(cell.pod.grace, \"ms\") + valueIn(cell.pod.preStop, \"ms\");\n await sleep(3000);\n while (Date.now() < limit && !over(ctx, { target, replicas, restarts }, lab.lines)) await sleep(250);\n // then until each predicted window can close and each request that started in it ended (`observationEnd`)\n const until = observationEnd(cell, { start, acts: { fault, heal, resume }, target: stoppedOf(cell, replicas, target, lab.lines), replicas, lines: lab.lines });\n if (until !== null) {\n const wait = Math.min(until - (await lab.now()), limit - Date.now());\n if (wait > 0) await sleep(wait);\n }\n const end = await lab.now();\n const timedOut = Date.now() >= limit;\n for (const step of plan.restore) {\n try {\n await doStep(ctx, step, replicas, background);\n } catch (error) {\n notes.push(`the restore ${step.do} failed: ${String(error)}`);\n }\n }\n ctx.expected = cell.pod.replicas;\n for (const note of notes) ctx.log(` ${note}`);\n const lines = lab.take();\n const stopped = stoppedOf(cell, replicas, target, lines);\n const restarted = lines.some((line) => line.ev === \"pod\" && (line[\"cs\"] as Array<{ rc: number }>).some((status) => status.rc > 0));\n if (restarted) {\n // a container that restarted gets a back-off on its next restart: new pods start without one\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"restart\", `deployment/${SERVER}`]);\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"status\", `deployment/${SERVER}`, \"--timeout=300s\"]);\n }\n if (cell.trigger.kind === \"node-drain\") await rebalance(ctx);\n ctx.log(` ${warmup ? \"warm-up\" : `run ${index}`}: ${sec(end - start)} s${timedOut ? \", not at rest in time\" : \"\"}`);\n return {\n env, index, warmup, start, end, acts: { fault, heal, resume }, target: stopped, paused, replicas, void: null, lines, dropped: { ...lab.dropped }, notes,\n };\n};\n\nconst sec = (ms: number): number => Math.round(ms) / 1000;\n\n/** This function moves a replica back to a node without one after a drain: it deletes one replica of the node with two. */\nconst rebalance = async (ctx: Context): Promise<void> => {\n await steady(ctx, 300_000);\n const servers = ctx.tracker.servers();\n const byNode = new Map<string, string[]>();\n for (const pod of servers) byNode.set(pod.node ?? \"\", [...(byNode.get(pod.node ?? \"\") ?? []), pod.name]);\n const crowded = [...byNode.values()].find((pods) => pods.length > 1);\n if (crowded !== undefined && byNode.size < ctx.cluster.nodes.length - 1)\n await ctx.cluster.kubectl([\"-n\", ctx.namespace, \"delete\", \"pod\", crowded[0]!, \"--wait=true\"]);\n};\n\n/** This function writes the result of a cell: its raw record, and one line of its verdict in `summary.ndjson`. */\nexport const writeResult = (out: string, result: KindCellResult): void => {\n mkdirSync(out, { recursive: true });\n const record: KindCellRecord = { format: FORMAT, ...result };\n writeFileSync(join(out, `${slug(result.cell.name)}.json`), JSON.stringify(record));\n const { verdict, cell } = result;\n writeFileSync(\n join(out, \"summary.ndjson\"),\n `${JSON.stringify({\n id: cell.id,\n name: cell.name,\n mode: cell.mode,\n trigger: cell.trigger.kind,\n prediction: cell.prediction.kind,\n decision: verdict.decision,\n counted: verdict.counted,\n voided: verdict.voided,\n failed: verdict.failed,\n median: verdict.median,\n why: verdict.why,\n })}\\n`,\n { flag: \"a\" },\n );\n};\n\n/**\n * This function does the runs of a catalog of kind cells. The cells of one kube-proxy mode and node\n * count share a cluster. Each group gets new clusters in sequence, one for each environment. Each\n * environment does its share of the runs of each cell, after one warm-up. The function deletes each\n * cluster after its cells.\n */\nexport const runKindCells = async (cells: readonly KindCell[], options: RunOptions): Promise<KindCellResult[]> => {\n const environments = options.environments ?? 2;\n const image = await labImage();\n const redisImage = await localPin(REDIS_IMAGE);\n // the server of the stack grpc: the image of grpc-go of the harness, built from its tag\n const grpcImage = cells.some((cell) => stackOf(cell) === \"grpc\") ? await imageOf({ kind: \"shipped\", name: \"grpc-go\" }) : undefined;\n // Istio: each image of each flavor of the cells pulled once by its digest, and tagged with it, for `kind load`\n const flavors = new Set(cells.flatMap((cell) => (cell.mesh === undefined ? [] : [cell.mesh.flavor])));\n const pinned = new Map<keyof typeof ISTIO_IMAGES, string>();\n for (const name of new Set([...flavors].flatMap((flavor) => MESH_IMAGES[flavor]))) pinned.set(name, await localPin(ISTIO_IMAGES[name]));\n const meshImages: MeshImages | undefined =\n flavors.size === 0 ? undefined : { pilot: pinned.get(\"pilot\")!, proxy: pinned.get(\"proxy\")!, cni: pinned.get(\"cni\"), ztunnel: pinned.get(\"ztunnel\") };\n const images: Images = { lab: image, redis: redisImage, grpc: grpcImage, mesh: meshImages };\n // the cells of one kube-proxy mode, node count and mesh flavor share a cluster\n const groups = new Map<string, KindCell[]>();\n for (const cell of cells) groups.set(clusterKey(cell), [...(groups.get(clusterKey(cell)) ?? []), cell]);\n const results: KindCellResult[] = [];\n for (const [key, group] of groups) {\n const partial = new Map<string, { runs: KindRun[]; manifests: EnvironmentManifest[]; loads: LoadSample[] }>();\n for (let env = 0; env < environments; env += 1) {\n const s = scenario(`lab-${key}-${env}`, group[0]!.mode, group[0]!.workers);\n const dir = join(options.work ?? join(tmpdir(), \"systems-kind-work\"), s.name);\n const mesh = group[0]!.mesh;\n options.log(`cluster ${s.name}: ${s.kubeProxy.mode}, ${s.workers} workers${mesh === undefined ? \"\" : `, Istio ${mesh.flavor}`}`);\n const cluster = await createCluster(s, dir);\n let lab: KindLab | null = null;\n let loader: StartedTestContainer | null = null;\n try {\n await cluster.load(image);\n await cluster.load(redisImage);\n if (grpcImage !== undefined && group.some((cell) => stackOf(cell) === \"grpc\")) await cluster.load(grpcImage);\n if (mesh !== undefined) {\n if (images.mesh === undefined) throw new Error(`${group[0]!.name}: the run has no images of Istio`);\n for (const name of MESH_IMAGES[mesh.flavor]) {\n const each = images.mesh[name];\n if (each === undefined) throw new Error(`${group[0]!.name}: the run has no image ${name} of Istio`);\n await cluster.load(each);\n }\n const began = Date.now();\n await installMesh(cluster, mesh.flavor, images.mesh);\n options.log(` Istio ${mesh.flavor} installed in ${Math.round((Date.now() - began) / 100) / 10} s`);\n }\n lab = await openKindLab(cluster, image);\n const { GenericContainer } = await import(\"testcontainers\");\n const Shared = class extends GenericContainer {\n withPidMode(mode: string): this {\n (this as unknown as { hostConfig: { PidMode?: string } }).hostConfig.PidMode = mode;\n return this;\n }\n };\n loader = await new Shared(image).withPidMode(\"host\").withNetworkMode(\"none\").withCommand([\"sleep\", \"infinity\"]).start();\n const manifest = await cluster.manifest();\n const ours = new Set<string>([...cluster.nodes]);\n for (const cell of group) {\n const namespace = namespaceOf(cell);\n options.log(`${cell.name} (${cell.id}), environment ${env}`);\n const tracker = new Tracker();\n const off = lab.listen(tracker.see);\n const loads: LoadSample[] = [];\n // the environment of ztunnel for the cell, before its pods: thus its rollout ends no flow of the cell\n const ztunnelEnv = cell.mesh?.ztunnel;\n try {\n if (ztunnelEnv !== undefined) await setZtunnelEnv(cluster, ztunnelEnv, false);\n const clusterIp = await deploy(lab, cluster, cell, namespace, images);\n for (const name of [...lab.own(), loader.getName().replace(/^\\//, \"\")]) ours.add(name);\n const ctx: Context = { lab, cluster, cell, namespace, tracker, clusterIp, loads, loader, idle: options.idle ?? 0.6, log: options.log, version: 1, probed: \"\", listened: \"\", expected: cell.pod.replicas, pids: new Map() };\n const share = Math.ceil(cell.runs / environments) - (env < cell.runs % environments || cell.runs % environments === 0 ? 0 : 1);\n const runs: KindRun[] = [await runOnce(ctx, env, -1, true, ours)];\n for (let index = 0; index < share; index += 1) runs.push(await runOnce(ctx, env, index, false, ours));\n const entry = partial.get(cell.name) ?? { runs: [], manifests: [], loads: [] };\n entry.runs.push(...runs);\n entry.manifests.push({\n env,\n scenario: s,\n cluster: manifest,\n images: {\n lab: image,\n redis: redisImage,\n ...(stackOf(cell) === \"grpc\" ? { grpc: grpcImage! } : {}),\n ...(cell.mesh === undefined ? {} : Object.fromEntries(MESH_IMAGES[cell.mesh.flavor].map((name) => [`istio-${name}`, ISTIO_IMAGES[name]]))),\n },\n });\n entry.loads.push(...loads);\n partial.set(cell.name, entry);\n mkdirSync(join(options.out, \"partial\"), { recursive: true });\n writeFileSync(join(options.out, \"partial\", `${slug(cell.name)}.env${env}.json`), JSON.stringify({ cell, runs, manifest, loads }));\n } finally {\n off();\n await lab.rules(null).catch(() => undefined);\n await lab.eds(null).catch(() => undefined);\n await lab.lds(null).catch(() => undefined);\n await lab.stopProbe();\n await cluster.kubectl([\"delete\", \"namespace\", namespace, \"--wait=true\", \"--timeout=300s\"]).catch((error: unknown) => options.log(` ${String(error)}`));\n if (ztunnelEnv !== undefined) await setZtunnelEnv(cluster, ztunnelEnv, true).catch((error: unknown) => options.log(` the undo of the environment of ztunnel failed: ${String(error)}`));\n }\n }\n } finally {\n if (loader !== null) await loader.stop({ timeout: 0 }).catch(() => undefined);\n if (lab !== null) await lab.close();\n await cluster.delete();\n options.log(`cluster ${s.name} deleted`);\n }\n }\n for (const cell of group) {\n const entry = partial.get(cell.name);\n if (entry === undefined) continue;\n const result: KindCellResult = { cell, ...entry, verdict: judgeKind(cell, entry.runs), at: new Date().toISOString() };\n writeResult(options.out, result);\n rmSync(join(options.out, \"partial\", `${slug(cell.name)}.env0.json`), { force: true });\n for (let env = 1; env < environments; env += 1) rmSync(join(options.out, \"partial\", `${slug(cell.name)}.env${env}.json`), { force: true });\n options.log(`${cell.name}: ${result.verdict.decision}, ${result.verdict.why}`);\n results.push(result);\n }\n }\n return results;\n};\n\n/** This function gives the estimate of a cell in seconds, for the arithmetic of the shards. */\nconst estimate = (cell: KindCell, environments = 2): number => {\n const hold = cell.trigger.hold === undefined ? 0 : valueIn(cell.trigger.hold, \"s\");\n const trigger: Record<string, number> = {\n \"liveness-restart\": 100,\n crash: 30,\n \"app-stall\": hold + 40,\n \"cpu-throttle\": hold + 40,\n rollout: hold + 50,\n \"authorization-policy\": hold + 20,\n idle: hold + 5,\n // the grace of 30 s of the chart of ztunnel, then the removal of the old pods\n \"ztunnel-restart\": 40,\n };\n // a window can need a longer observation: from the end of the hold, until each request that started in it ended\n const after = observationAfter(cell);\n const observed = after === null ? 0 : hold + valueIn(after, \"s\");\n // the band of `ends` too: until each socket can end\n const closes = cell.prediction.kind === \"ends\" && cell.prediction.band !== null ? valueIn(cell.prediction.band.hi, \"s\") + 3 : 0;\n const each = 25 + Math.max(observed, closes, trigger[cell.trigger.kind] ?? (cell.template === \"T18\" ? hold + 35 : 15 + valueIn(cell.pod.preStop, \"s\")));\n // the install of Istio in each cluster takes 1 to 2 min\n const setup = cell.mesh === undefined ? 60 : 180;\n return setup * environments + (cell.runs + environments) * each;\n};\n\n/**\n * This function gives an estimate of the time of a cell: its setup, then each run and each\n * warm-up. A run waits for the cell at rest, samples the load, does the trigger and waits until the\n * cell is at rest again.\n */\nexport const estimatedTime = (cell: KindCell, environments = 2): Quantity => ({ value: estimate(cell, environments), unit: \"s\" });\n\n/**\n * This function gives the share of a shard of a catalog: cells in sequence. Thus the cells of one\n * mode and one mesh flavor stay together and share clusters. Each shard gets an equal share of the\n * estimated time. In one mode, the cells with no mesh come first, in the sequence of the catalog.\n */\nexport const shardOf = <T extends KindCell>(cells: readonly T[], index: number, count: number): T[] => {\n if (!(Number.isInteger(index) && Number.isInteger(count) && count >= 1 && index >= 0 && index < count))\n throw new RangeError(`shard ${index} of ${count} is not a shard`);\n const rank = (cell: KindCell): string => `${cell.mode}/${meshKey(cell) === \"none\" ? \"\" : meshKey(cell)}`;\n const ordered = [...cells].sort((a, b) => (rank(a) === rank(b) ? 0 : rank(a) < rank(b) ? -1 : 1));\n const total = ordered.reduce((sum, cell) => sum + estimate(cell), 0);\n const out: T[] = [];\n let before = 0;\n for (const cell of ordered) {\n const middle = before + estimate(cell) / 2;\n if (Math.min(count - 1, Math.floor((middle / total) * count)) === index) out.push(cell);\n before += estimate(cell);\n }\n return out;\n};\n"],"mappings":"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"}
|