@mark1russell7/netdiag 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +153 -1
  2. package/build/address.d.ts +56 -0
  3. package/build/address.d.ts.map +1 -0
  4. package/build/address.js +172 -0
  5. package/build/address.js.map +1 -0
  6. package/build/{diagnosis-BTa-Dfwl.js → diagnosis-a4hRzFxp.js} +113 -11
  7. package/build/diagnosis-a4hRzFxp.js.map +1 -0
  8. package/build/diagnosis.d.ts +3 -2
  9. package/build/diagnosis.js +1 -1
  10. package/build/{index-CP1hzELI.d.ts → index-K2dGiYxW.d.ts} +83 -22
  11. package/build/index-K2dGiYxW.d.ts.map +1 -0
  12. package/build/index.d.ts +253 -148
  13. package/build/index.d.ts.map +1 -1
  14. package/build/index.js +1084 -161
  15. package/build/index.js.map +1 -1
  16. package/build/kinds-CQv769lK.js +341 -0
  17. package/build/kinds-CQv769lK.js.map +1 -0
  18. package/build/kinds-_NShfZpU.d.ts +377 -0
  19. package/build/kinds-_NShfZpU.d.ts.map +1 -0
  20. package/build/layer-BNg0VYuK.d.ts +22 -0
  21. package/build/layer-BNg0VYuK.d.ts.map +1 -0
  22. package/build/tools.d.ts +206 -0
  23. package/build/tools.d.ts.map +1 -0
  24. package/build/tools.js +896 -0
  25. package/build/tools.js.map +1 -0
  26. package/package.json +17 -5
  27. package/src/address/index.ts +155 -0
  28. package/src/diagnosis/catalog.ts +8 -8
  29. package/src/diagnosis/index.ts +13 -0
  30. package/src/diagnosis/score.ts +160 -0
  31. package/src/index.ts +35 -0
  32. package/src/kinds.ts +460 -0
  33. package/src/probes/native.ts +354 -0
  34. package/src/probes/socket.ts +520 -0
  35. package/src/readers.ts +367 -0
  36. package/src/scope.ts +5 -24
  37. package/src/tools/common.ts +47 -0
  38. package/src/tools/host.ts +91 -0
  39. package/src/tools/index.ts +38 -0
  40. package/src/tools/licenses.ts +37 -0
  41. package/src/tools/mtr.ts +156 -0
  42. package/src/tools/ping.ts +231 -0
  43. package/src/tools/traceroute.ts +313 -0
  44. package/build/diagnosis-BTa-Dfwl.js.map +0 -1
  45. package/build/index-CP1hzELI.d.ts.map +0 -1
@@ -0,0 +1,313 @@
1
+ import { isIP } from "node:net";
2
+ import { observe, type Observation } from "../fact.ts";
3
+ import { PATH_TRACE, type PathTrace, type TraceHop } from "../kinds.ts";
4
+ import { defineProbe, type Availability, type Probe, type ProbeContext } from "../probe.ts";
5
+ import { target, type Target } from "../target.ts";
6
+ import { ENGLISH, firstLine, integerIn, LINES, LOCAL_UNREACHABLE, sameAddress, toolAddress, type ToolAddress } from "./common.ts";
7
+ import { AVAILABILITY_TIMEOUT_MS, availabilityOf, runTool, TOOL_MARGIN_MS, type ToolCommand, type ToolHost, type ToolRaw } from "./host.ts";
8
+
9
+ // The adapter of the tool traceroute. It reads the English output of the Modern traceroute for Linux, of the BSD
10
+ // traceroute and traceroute6 on macOS, and of tracert.exe on Windows. Each tool sends `TRACE_QUERIES` probes for each TTL.
11
+
12
+ /** The input of the traceroute probe: the path to one IP address. */
13
+ export interface TraceInput {
14
+ readonly address: string;
15
+ /** The largest TTL. The default is `DEFAULT_TRACE_HOPS`. */
16
+ readonly maxHops?: number | undefined;
17
+ /** The time to wait for each answer, in milliseconds. On Linux and macOS, the tool rounds it up to full seconds. */
18
+ readonly waitMs?: number | undefined;
19
+ }
20
+
21
+ /** The default largest TTL. */
22
+ export const DEFAULT_TRACE_HOPS = 30;
23
+
24
+ /** The largest TTL of one input. */
25
+ export const MAX_TRACE_HOPS = 64;
26
+
27
+ /** The default time to wait for each answer, in milliseconds. */
28
+ export const DEFAULT_TRACE_WAIT_MS = 1000;
29
+
30
+ /** The number of probes for each TTL. `tracert` on Windows always sends 3 probes. */
31
+ export const TRACE_QUERIES = 3;
32
+
33
+ interface CheckedInput extends ToolAddress {
34
+ readonly maxHops: number;
35
+ readonly waitMs: number;
36
+ }
37
+
38
+ function checked(input: TraceInput): CheckedInput {
39
+ return {
40
+ ...toolAddress(input.address),
41
+ maxHops: integerIn(input.maxHops ?? DEFAULT_TRACE_HOPS, 1, MAX_TRACE_HOPS, "a number of hops"),
42
+ waitMs: integerIn(input.waitMs ?? DEFAULT_TRACE_WAIT_MS, 1, 10_000, "a time in milliseconds"),
43
+ };
44
+ }
45
+
46
+ /**
47
+ * This function gives the command of traceroute for an operating system and an input. For an operating system that the
48
+ * adapter does not know, it gives `undefined`. It throws for an input that is not valid.
49
+ *
50
+ * - Linux: `traceroute`, with UDP probes. A user without privileges can send them.
51
+ * - macOS: `traceroute`, and `traceroute6` for an IPv6 address.
52
+ * - Windows: `tracert.exe`, with ICMP echo requests.
53
+ *
54
+ * The time limit is the time of a trace that waits for each probe of each hop.
55
+ */
56
+ export function tracerouteCommand(platform: string, input: TraceInput): ToolCommand | undefined {
57
+ const { address, family, maxHops, waitMs } = checked(input);
58
+ const waitS = Math.ceil(waitMs / 1000);
59
+ const unix = ["-n", "-q", String(TRACE_QUERIES), "-w", String(waitS), "-m", String(maxHops)];
60
+ const unixTimeoutMs = maxHops * TRACE_QUERIES * waitS * 1000 + TOOL_MARGIN_MS;
61
+ switch (platform) {
62
+ case "linux":
63
+ return { command: "traceroute", args: [family === "ipv6" ? "-6" : "-4", ...unix, address], timeoutMs: unixTimeoutMs, env: ENGLISH };
64
+ case "darwin":
65
+ return { command: family === "ipv6" ? "traceroute6" : "traceroute", args: [...unix, address], timeoutMs: unixTimeoutMs, env: ENGLISH };
66
+ case "win32":
67
+ return { command: "tracert", args: ["-d", "-h", String(maxHops), "-w", String(waitMs), address], timeoutMs: maxHops * TRACE_QUERIES * waitMs + TOOL_MARGIN_MS };
68
+ default:
69
+ return undefined;
70
+ }
71
+ }
72
+
73
+ /** The command that examines if traceroute is available on each operating system, and the pattern of its version. */
74
+ const AVAILABILITY: Readonly<Record<string, { readonly command: string; readonly args: readonly string[]; readonly version?: RegExp }>> = {
75
+ linux: { command: "traceroute", args: ["--version"], version: /version (\d[\d.]*)/ },
76
+ darwin: { command: "traceroute", args: [] },
77
+ win32: { command: "tracert", args: ["/?"] },
78
+ };
79
+
80
+ /** This function tells if traceroute is available on the host. On Linux, netdiag reads only the Modern traceroute for Linux. */
81
+ export async function tracerouteAvailability(host: ToolHost, context: ProbeContext): Promise<Availability> {
82
+ const check = AVAILABILITY[host.platform];
83
+ if (check === undefined) return { available: false, reason: `netdiag reads the output of traceroute only on Linux, macOS and Windows, not on ${host.platform}.` };
84
+ const output = await host.run(check.command, check.args, { timeoutMs: AVAILABILITY_TIMEOUT_MS, signal: context.signal, env: ENGLISH });
85
+ if (host.platform === "linux" && output.error === undefined && !/Modern traceroute/i.test(`${output.stdout}\n${output.stderr}`)) {
86
+ return { available: false, reason: "netdiag reads only the Modern traceroute for Linux (the package traceroute). This traceroute does not tell that name." };
87
+ }
88
+ return availabilityOf(check.command, output, check.version);
89
+ }
90
+
91
+ /** The flags of the Linux and BSD traceroute after the time of an answer, and their meaning. */
92
+ const FLAGS: Readonly<Record<string, string>> = {
93
+ "!H": "host unreachable",
94
+ "!N": "network unreachable",
95
+ "!P": "protocol unreachable",
96
+ "!S": "source route failed",
97
+ "!F": "fragmentation needed",
98
+ "!X": "communication administratively prohibited",
99
+ "!A": "communication with the network administratively prohibited",
100
+ "!Z": "communication with the host administratively prohibited",
101
+ "!U": "destination network unknown",
102
+ "!W": "destination host unknown",
103
+ "!V": "host precedence violation",
104
+ "!C": "precedence cutoff",
105
+ };
106
+
107
+ /** This function gives the meaning of a flag, or `undefined` for the flag `!` alone. That flag tells only that the TTL of the answer is 1 or less. */
108
+ function flagText(flag: string): string | undefined {
109
+ if (flag === "!") return undefined;
110
+ const code = /^!<?(\d+)>?$/.exec(flag)?.[1];
111
+ if (code !== undefined) return `unreachable, ICMP code ${code} (${flag})`;
112
+ const known = FLAGS[flag.replace(/^(!F)-?\d+$/, "$1")];
113
+ return `${known ?? "unreachable"} (${flag})`;
114
+ }
115
+
116
+ /** The hops that netdiag reads in the output of a trace, and the facts about the output. */
117
+ interface TraceLines {
118
+ readonly hops: readonly TraceHop[];
119
+ /** This field is true when the output has the header of the tool. Output in another language has no such header. */
120
+ readonly header: boolean;
121
+ /** This field is true when the output has the last line of a trace (`tracert` only). */
122
+ readonly complete: boolean;
123
+ /** The first hop that netdiag cannot read. */
124
+ readonly unread?: number | undefined;
125
+ /** A failure of this computer to send a probe. */
126
+ readonly failure?: string | undefined;
127
+ /** A message of this computer that tells that the address is unreachable. */
128
+ readonly local?: string | undefined;
129
+ }
130
+
131
+ /** One hop while the reader reads it. */
132
+ class HopBuilder {
133
+ readonly addresses: string[] = [];
134
+ readonly rttMs: number[] = [];
135
+ sent = 0;
136
+ received = 0;
137
+ unreachable: string | undefined;
138
+ /** This field is true when a time comes before an address. */
139
+ unread = false;
140
+ readonly ttl: number;
141
+ private current: string | undefined;
142
+
143
+ constructor(ttl: number) {
144
+ this.ttl = ttl;
145
+ }
146
+
147
+ address(text: string): void {
148
+ this.current = text;
149
+ if (!this.addresses.some((known) => sameAddress(known, text))) this.addresses.push(text);
150
+ }
151
+
152
+ lost(): void {
153
+ this.sent += 1;
154
+ }
155
+
156
+ answer(rttMs: number | undefined): void {
157
+ this.sent += 1;
158
+ if (this.current === undefined) {
159
+ this.unread = true;
160
+ return;
161
+ }
162
+ this.received += 1;
163
+ if (rttMs !== undefined) this.rttMs.push(rttMs);
164
+ }
165
+
166
+ hop(): TraceHop {
167
+ const hop = { ttl: this.ttl, addresses: [...this.addresses], sent: this.sent, received: this.received, rttMs: [...this.rttMs] };
168
+ return this.unreachable === undefined ? hop : { ...hop, unreachable: this.unreachable };
169
+ }
170
+ }
171
+
172
+ /** This function gives the address of a token, for example `192.0.2.1` or `(192.0.2.1)`, or `undefined`. */
173
+ function addressOf(token: string): string | undefined {
174
+ const text = token.replace(/^\((.*)\)$/, "$1");
175
+ return isIP(text) === 0 ? undefined : text;
176
+ }
177
+
178
+ /** The messages of traceroute on Linux and macOS when a socket operation fails, for example `socket: Operation not permitted`. */
179
+ const SEND_FAILURE = /^(?:socket|bind|connect|sendmsg|sendto|send): \S/;
180
+
181
+ function readUnix(stdout: string, stderr: string): TraceLines {
182
+ const builders: HopBuilder[] = [];
183
+ for (const line of stdout.split(LINES)) {
184
+ const numbered = /^\s*(\d+)\s(.*)$/.exec(line);
185
+ // macOS writes another address of the same TTL on a line with no number.
186
+ const continued = numbered === null && /^\s+\S/.test(line) ? builders.at(-1) : undefined;
187
+ if (numbered === null && continued === undefined) continue;
188
+ const builder = continued ?? new HopBuilder(Number(numbered?.[1]));
189
+ if (continued === undefined) builders.push(builder);
190
+ const tokens = (numbered?.[2] ?? line).trim().split(/\s+/);
191
+ for (let i = 0; i < tokens.length; i += 1) {
192
+ const token = tokens[i] ?? "";
193
+ const address = addressOf(token);
194
+ const time = /^(\d+(?:\.\d+)?)(ms)?$/.exec(token);
195
+ if (token === "*") builder.lost();
196
+ else if (address !== undefined) builder.address(address);
197
+ else if (time !== null && (time[2] === "ms" || tokens[i + 1] === "ms")) {
198
+ builder.answer(Number(time[1]));
199
+ if (time[2] === undefined) i += 1;
200
+ } else if (token.startsWith("!")) builder.unreachable ??= flagText(token);
201
+ }
202
+ }
203
+ let failure: string | undefined;
204
+ let local: string | undefined;
205
+ for (const line of stderr.split(LINES)) {
206
+ const text = line.replace(/^traceroute6?:\s*/, "").trim();
207
+ const unreachable = LOCAL_UNREACHABLE.exec(text);
208
+ if (unreachable !== null) local ??= unreachable[1] ?? text;
209
+ else if (SEND_FAILURE.test(text)) failure ??= text;
210
+ }
211
+ const header = /^traceroute6? to /m.test(`${stdout}\n${stderr}`);
212
+ return { hops: builders.map((b) => b.hop()), header, complete: true, unread: builders.find((b) => b.unread)?.ttl, failure, local };
213
+ }
214
+
215
+ /** The text between the address of a router and its message in the output of `tracert`. */
216
+ const REPORTS = " reports: ";
217
+
218
+ function readWindows(stdout: string): TraceLines {
219
+ const builders: HopBuilder[] = [];
220
+ let failure: string | undefined;
221
+ for (const line of stdout.split(LINES)) {
222
+ const numbered = /^\s*(\d+)\s(.*)$/.exec(line);
223
+ if (numbered === null) continue;
224
+ const builder = new HopBuilder(Number(numbered[1]));
225
+ builders.push(builder);
226
+ // " 2 * * 192.168.1.191 reports: Destination host unreachable."
227
+ const rest = (numbered[2] ?? "").trim();
228
+ const at = rest.indexOf(REPORTS);
229
+ const tokens = (at < 0 ? rest : rest.slice(0, at)).split(/\s+/).filter((token) => token.length > 0);
230
+ // tracert writes the address after the times of the hop.
231
+ const address = tokens.map(addressOf).find((found) => found !== undefined);
232
+ if (address !== undefined) builder.address(address);
233
+ for (let i = 0; i < tokens.length; i += 1) {
234
+ const token = tokens[i] ?? "";
235
+ // "<1 ms" counts as 1 ms, as in the output of ping.
236
+ const time = /^<?(\d+)$/.exec(token);
237
+ if (token === "*") builder.lost();
238
+ else if (time !== null && tokens[i + 1] === "ms") {
239
+ builder.answer(Math.max(1, Number(time[1])));
240
+ i += 1;
241
+ } else if (/^(General failure|Transmit error)/i.test(tokens.slice(i).join(" "))) {
242
+ failure ??= tokens.slice(i).join(" ").replace(/\.$/, "");
243
+ break;
244
+ }
245
+ }
246
+ if (at >= 0) {
247
+ builder.answer(undefined);
248
+ builder.unreachable = rest
249
+ .slice(at + REPORTS.length)
250
+ .trim()
251
+ .replace(/\.$/, "");
252
+ }
253
+ }
254
+ return {
255
+ hops: builders.map((b) => b.hop()),
256
+ header: /^Tracing route to /m.test(stdout),
257
+ complete: /^Trace complete\./m.test(stdout),
258
+ unread: builders.find((b) => b.unread)?.ttl,
259
+ failure,
260
+ };
261
+ }
262
+
263
+ /**
264
+ * This function reads the raw output of the traceroute probe into the value of a fact `path.trace`.
265
+ *
266
+ * - The outcome is `reached` when the address answered at the last hop.
267
+ * - Output that netdiag cannot read gives the outcome `error`. An `error` keeps the hops that netdiag read.
268
+ */
269
+ export function readTraceroute(raw: ToolRaw, input: { readonly address: string; readonly maxHops?: number | undefined }): PathTrace {
270
+ const { address } = input;
271
+ const maxHops = input.maxHops ?? DEFAULT_TRACE_HOPS;
272
+ const result = (outcome: PathTrace["outcome"], hops: readonly TraceHop[], detail?: string): PathTrace =>
273
+ detail === undefined ? { address, outcome, maxHops, hops } : { address, outcome, maxHops, hops, detail };
274
+ if (raw.error !== undefined) return result("error", [], raw.error);
275
+ const lines = raw.platform === "win32" ? readWindows(raw.stdout) : readUnix(raw.stdout, raw.stderr);
276
+ const { hops, header, complete, unread, failure, local } = lines;
277
+ if (unread !== undefined) return result("error", hops.filter((hop) => hop.ttl < unread), `netdiag cannot read the hop ${unread} of traceroute.`);
278
+ // Output in another language has numbered lines too, but no English header.
279
+ if (!header && local === undefined) {
280
+ return result("error", [], failure ?? firstLine(raw.stderr) ?? "netdiag cannot read the output of traceroute. netdiag reads only English output.");
281
+ }
282
+ if (raw.timedOut === true || !complete) return result("error", hops, "The time limit stopped traceroute before its end.");
283
+ const last = hops.at(-1);
284
+ if (last?.addresses.some((known) => sameAddress(known, address)) === true) return result("reached", hops);
285
+ if (last?.unreachable !== undefined) return result("unreachable", hops, last.unreachable);
286
+ if (local !== undefined) return result("unreachable", hops, local);
287
+ if (failure !== undefined) return result("error", hops, failure);
288
+ return result("stopped", hops);
289
+ }
290
+
291
+ /**
292
+ * This function makes the probe `path.traceroute`: the trace of the path to one IP address with the tool traceroute,
293
+ * or `tracert` on Windows. The host starts the tool.
294
+ */
295
+ export function tracerouteProbe(host: ToolHost): Probe<TraceInput, ToolRaw> {
296
+ return defineProbe<TraceInput, ToolRaw>({
297
+ id: "path.traceroute",
298
+ version: "1",
299
+ summary: "The hops of the path to one IP address, from the tool traceroute or tracert.",
300
+ layer: "path",
301
+ mode: "active",
302
+ kinds: [PATH_TRACE],
303
+ tool: "traceroute",
304
+ peers: (input): readonly Target[] => [target(checked(input).address)],
305
+ available: (context) => tracerouteAvailability(host, context),
306
+ async run(input, context) {
307
+ const command = tracerouteCommand(host.platform, input);
308
+ if (command === undefined) throw new Error(`netdiag has no command of traceroute for ${host.platform}.`);
309
+ return runTool(host, command, context);
310
+ },
311
+ parse: (raw, input): readonly Observation[] => [observe(PATH_TRACE, target(input.address), readTraceroute(raw, input), { start: raw.start, end: raw.end })],
312
+ });
313
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"diagnosis-BTa-Dfwl.js","names":[],"sources":["../src/layer.ts","../src/diagnosis/model.ts","../src/diagnosis/catalog.ts"],"sourcesContent":["/**\n * The layers of the network stack that netdiag examines, from the bottom to the top.\n *\n * - `link`: the network interface and its physical link.\n * - `wifi`: the Wi-Fi link: the signal, the channel and the rate.\n * - `ip`: the addresses of the interfaces.\n * - `route`: the routes of this computer and its gateway.\n * - `path`: the hops through the provider networks to the remote host.\n * - `dns`: the resolution of names to addresses.\n * - `tcp`: the connections to a port.\n * - `tls`: the handshake and the certificate of the server.\n * - `http`: the request and the response.\n */\nexport const LAYERS = [\"link\", \"wifi\", \"ip\", \"route\", \"path\", \"dns\", \"tcp\", \"tls\", \"http\"] as const;\n\n/** A layer of the network stack. The list `LAYERS` gives each layer in its order. */\nexport type Layer = (typeof LAYERS)[number];\n\nconst RANKS: ReadonlyMap<string, number> = new Map(LAYERS.map((layer, rank) => [layer, rank]));\n\n/** This function tells if a value is the name of a layer. */\nexport function isLayer(value: unknown): value is Layer {\n return typeof value === \"string\" && RANKS.has(value);\n}\n\n/** This function gives the position of a layer in `LAYERS`. The bottom layer `link` has the position 0. */\nexport function layerRank(layer: Layer): number {\n const rank = RANKS.get(layer);\n if (rank === undefined) throw new RangeError(`\"${String(layer)}\" is not a layer`);\n return rank;\n}\n","import { err, ok, type Result } from \"@mark1russell7/optional\";\nimport type { Layer } from \"../layer.ts\";\n\n/** A probability for each value of a feature. The probabilities are more than 0, and their sum is 1. */\nexport type Distribution = Readonly<Record<string, number>>;\n\n/**\n * A feature is one discrete observation that the facts give, for example the outcome of a TCP connect.\n *\n * The distribution `healthy` gives the probability of each value when the network has no fault.\n */\nexport interface Feature {\n readonly id: string;\n readonly layer: Layer;\n readonly summary: string;\n readonly values: readonly string[];\n readonly healthy: Distribution;\n}\n\n/** The parts of the model. Each fault is in one part. */\nexport const PARTS = [\"interface\", \"wifi\", \"gateway\", \"resolver\", \"path\", \"service\"] as const;\n\n/** A part of the model: the network interface, the Wi-Fi link, the gateway, the DNS resolver, the path or the remote service. */\nexport type Part = (typeof PARTS)[number];\n\n/**\n * The reach of a fault.\n *\n * - `shared`: the fault changes the observations of each host, for example a TLS proxy of the local network.\n * - `target`: the fault changes only the observations of the primary target, for example a slow server.\n */\nexport type Reach = \"shared\" | \"target\";\n\n/**\n * The strength of a prediction. The rules of practice use these strengths. The lab runs then replace them with fitted\n * distributions.\n */\nexport type Strength = \"sure\" | \"strong\" | \"usual\" | \"weak\";\n\n/** The probability that each strength gives to the expected values together. */\nexport const STRENGTHS: Readonly<Record<Strength, number>> = { sure: 0.98, strong: 0.9, usual: 0.75, weak: 0.6 };\n\n/**\n * A prediction of a fault for one feature: a full distribution, or the expected values and a strength.\n *\n * With `expect`, the expected values share the probability of the strength equally. The other values share the rest.\n */\nexport type Prediction = Distribution | Expectation;\n\n/** The expected values of a feature, and the strength of the prediction. */\nexport interface Expectation {\n readonly expect: string | readonly string[];\n readonly strength: Strength;\n}\n\nfunction isExpectation(prediction: Prediction): prediction is Expectation {\n return typeof prediction.strength === \"string\";\n}\n\n/**\n * A fault of the catalog.\n *\n * The fault predicts a distribution for each feature that it changes. For each other feature, it predicts the\n * distribution `healthy` of the feature. The prior is a relative weight. The default prior is 1.\n */\nexport interface Fault {\n readonly id: string;\n readonly part: Part;\n readonly reach: Reach;\n /** The cause in plain words, for example \"No program listens on the port.\" */\n readonly summary: string;\n /** The next step for the user in plain words. */\n readonly step: string;\n readonly prior?: number | undefined;\n readonly predicts: Readonly<Record<string, Prediction>>;\n}\n\n/** The data of a catalog: the features, the faults, and the priors of the hypotheses \"no fault\" and \"unknown cause\". */\nexport interface CatalogSpec {\n readonly version: string;\n readonly features: readonly Feature[];\n readonly faults: readonly Fault[];\n readonly priors?: { readonly none?: number | undefined; readonly unknown?: number | undefined } | undefined;\n}\n\n/**\n * A hypothesis of the model.\n *\n * - `none`: the network has no fault.\n * - `fault`: one fault of the catalog.\n * - `unknown`: a cause that the catalog does not have. It gives each value of a feature the same probability.\n */\nexport interface Hypothesis {\n readonly id: string;\n readonly kind: \"none\" | \"fault\" | \"unknown\";\n readonly summary: string;\n readonly step: string;\n readonly part?: Part | undefined;\n readonly reach?: Reach | undefined;\n}\n\n/** The id of the hypothesis \"no fault\". */\nexport const NONE = \"none\";\n/** The id of the hypothesis \"unknown cause\". */\nexport const UNKNOWN = \"unknown\";\n\n/** A catalog with each distribution in its full form, and the logarithms of the probabilities. */\nexport interface Catalog {\n readonly version: string;\n readonly features: ReadonlyMap<string, Feature>;\n readonly hypotheses: readonly Hypothesis[];\n /** This function gives the probability of a value of a feature under a hypothesis, for a primary or a reference host. */\n likelihood(hypothesis: string, feature: string, value: string, primary: boolean): number;\n prior(hypothesis: string): number;\n}\n\n/** The lowest probability of a value. With a probability of 0, one wrong observation removes a cause. */\nexport const FLOOR = 0.001;\n\nconst SUM_TOLERANCE = 1e-9;\n\nfunction distributionProblems(where: string, feature: Feature, dist: Distribution): string[] {\n const problems: string[] = [];\n for (const value of Object.keys(dist)) if (!feature.values.includes(value)) problems.push(`${where}: \"${value}\" is not a value of ${feature.id}.`);\n for (const value of feature.values) {\n const p = dist[value];\n if (p === undefined) problems.push(`${where}: the value \"${value}\" of ${feature.id} has no probability.`);\n else if (!Number.isFinite(p) || p < FLOOR) problems.push(`${where}: the probability of \"${value}\" is ${p}. It must be ${FLOOR} or more.`);\n }\n const sum = Object.values(dist).reduce((a, b) => a + b, 0);\n if (Math.abs(sum - 1) > SUM_TOLERANCE) problems.push(`${where}: the sum of the probabilities is ${sum}, not 1.`);\n return problems;\n}\n\n/** This function gives the full distribution of a prediction, or the problems of the prediction. */\nexport function expand(feature: Feature, prediction: Prediction): Result<Distribution, string[]> {\n if (!isExpectation(prediction)) return ok(prediction);\n const expected = typeof prediction.expect === \"string\" ? [prediction.expect] : [...prediction.expect];\n const unknown = expected.filter((value) => !feature.values.includes(value));\n if (unknown.length > 0) return err(unknown.map((value) => `\"${value}\" is not a value of ${feature.id}.`));\n if (expected.length === 0 || new Set(expected).size !== expected.length) return err([`The expected values of ${feature.id} are empty or repeated.`]);\n const others = feature.values.filter((value) => !expected.includes(value));\n const mass = others.length === 0 ? 1 : STRENGTHS[prediction.strength];\n const dist: Record<string, number> = {};\n for (const value of expected) dist[value] = mass / expected.length;\n for (const value of others) dist[value] = (1 - mass) / others.length;\n return ok(dist);\n}\n\nconst ID = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/;\n\n/**\n * This function examines a catalog and makes its tables.\n *\n * It refuses a repeated id, an unknown feature or value, a probability below `FLOOR`, and a distribution with a sum that is not 1.\n */\nexport function compileCatalog(spec: CatalogSpec): Result<Catalog, string[]> {\n const problems: string[] = [];\n const features = new Map<string, Feature>();\n for (const feature of spec.features) {\n if (!ID.test(feature.id)) problems.push(`\"${feature.id}\" is not a valid feature id.`);\n if (features.has(feature.id)) problems.push(`The feature ${feature.id} is repeated.`);\n if (feature.values.length < 2 || new Set(feature.values).size !== feature.values.length) {\n problems.push(`The feature ${feature.id} must have two or more different values.`);\n }\n problems.push(...distributionProblems(`healthy of ${feature.id}`, feature, feature.healthy));\n features.set(feature.id, feature);\n }\n const hypotheses: Hypothesis[] = [{ id: NONE, kind: \"none\", summary: \"The network has no fault.\", step: \"No step is necessary.\" }];\n const priors = new Map<string, number>([[NONE, spec.priors?.none ?? 1]]);\n const tables = new Map<string, Map<string, Distribution>>();\n for (const fault of spec.faults) {\n if (!ID.test(fault.id) || fault.id === NONE || fault.id === UNKNOWN) problems.push(`\"${fault.id}\" is not a valid fault id.`);\n if (priors.has(fault.id)) problems.push(`The fault ${fault.id} is repeated.`);\n priors.set(fault.id, fault.prior ?? 1);\n hypotheses.push({ id: fault.id, kind: \"fault\", summary: fault.summary, step: fault.step, part: fault.part, reach: fault.reach });\n const table = new Map<string, Distribution>();\n for (const [featureId, prediction] of Object.entries(fault.predicts)) {\n const feature = features.get(featureId);\n if (feature === undefined) {\n problems.push(`The fault ${fault.id} predicts the unknown feature ${featureId}.`);\n continue;\n }\n const dist = expand(feature, prediction);\n if (dist.tag === \"err\") problems.push(...dist.error.map((problem) => `${fault.id}: ${problem}`));\n else {\n problems.push(...distributionProblems(`${fault.id} for ${featureId}`, feature, dist.value));\n table.set(featureId, dist.value);\n }\n }\n if (table.size === 0) problems.push(`The fault ${fault.id} predicts no feature.`);\n tables.set(fault.id, table);\n }\n hypotheses.push({\n id: UNKNOWN,\n kind: \"unknown\",\n summary: \"The catalog does not have the cause.\",\n step: \"Collect more facts, and report the facts as a new fault of the catalog.\",\n });\n priors.set(UNKNOWN, spec.priors?.unknown ?? 1);\n for (const [id, prior] of priors) if (!Number.isFinite(prior) || prior <= 0) problems.push(`The prior of ${id} is ${prior}. It must be more than 0.`);\n if (problems.length > 0) return err(problems);\n\n const total = [...priors.values()].reduce((a, b) => a + b, 0);\n const reach = new Map(hypotheses.map((h) => [h.id, h.reach]));\n return ok({\n version: spec.version,\n features,\n hypotheses,\n prior: (hypothesis) => (priors.get(hypothesis) ?? 0) / total,\n likelihood(hypothesis, featureId, value, primary) {\n const feature = features.get(featureId);\n if (feature === undefined || !feature.values.includes(value)) return Number.NaN;\n if (hypothesis === UNKNOWN) return 1 / feature.values.length;\n const fault = tables.get(hypothesis);\n const changed = fault?.get(featureId);\n const applies = primary || reach.get(hypothesis) === \"shared\";\n return (applies && changed !== undefined ? changed : feature.healthy)[value] ?? Number.NaN;\n },\n });\n}\n\n/**\n * One observation of a feature.\n *\n * `primary` tells if the observation is of the primary target or of a reference host. `facts` gives the ids of the facts\n * that gave the observation, for the explanation.\n */\nexport interface Evidence {\n readonly feature: string;\n readonly value: string;\n readonly primary: boolean;\n readonly facts?: readonly string[] | undefined;\n}\n\n/** A cause with its probability. */\nexport interface Cause extends Hypothesis {\n readonly probability: number;\n}\n\n/** The result of a diagnosis: the causes from the most probable, and the evidence that the model did not use, with the reason. */\nexport interface Diagnosis {\n readonly causes: readonly Cause[];\n readonly used: readonly Evidence[];\n readonly ignored: readonly { readonly evidence: Evidence; readonly reason: string }[];\n}\n\nfunction usable(catalog: Catalog, evidence: readonly Evidence[]): Pick<Diagnosis, \"used\" | \"ignored\"> {\n const used: Evidence[] = [];\n const ignored: { evidence: Evidence; reason: string }[] = [];\n for (const item of evidence) {\n const feature = catalog.features.get(item.feature);\n if (feature === undefined) ignored.push({ evidence: item, reason: `The catalog has no feature ${item.feature}.` });\n else if (!feature.values.includes(item.value)) ignored.push({ evidence: item, reason: `\"${item.value}\" is not a value of ${item.feature}.` });\n else used.push(item);\n }\n return { used, ignored };\n}\n\n/**\n * This function gives the probability of each hypothesis from the evidence, with the rule of Bayes.\n *\n * The model takes the observations as independent for a given hypothesis. The calculation uses logarithms, so a long\n * list of evidence does not go to zero. The order of the evidence does not change the result.\n * An exact tie keeps the order of the catalog, with \"no fault\" first.\n */\nexport function diagnose(catalog: Catalog, evidence: readonly Evidence[]): Diagnosis {\n const { used, ignored } = usable(catalog, evidence);\n const logs = catalog.hypotheses.map((h) => {\n let sum = Math.log(catalog.prior(h.id));\n for (const item of used) sum += Math.log(catalog.likelihood(h.id, item.feature, item.value, item.primary));\n return sum;\n });\n const top = Math.max(...logs);\n const weights = logs.map((log) => Math.exp(log - top));\n const total = weights.reduce((a, b) => a + b, 0);\n // An exact tie keeps the order of the catalog: \"no fault\" first, then the faults, then \"unknown cause\". Thus a fault\n // whose features are not observed does not come before \"no fault\".\n const causes = catalog.hypotheses\n .map((h, i) => ({ ...h, probability: (weights[i] ?? 0) / total, order: i }))\n .sort((a, b) => b.probability - a.probability || a.order - b.order)\n .map(({ order: _order, ...cause }) => cause);\n return { causes, used, ignored };\n}\n\n/** The part of one observation in the comparison of two hypotheses. */\nexport interface Contribution {\n readonly evidence: Evidence;\n /** The ratio of the likelihoods of the two hypotheses, as a base-10 logarithm. 1 is a factor of 10 for the first hypothesis. */\n readonly weight: number;\n}\n\n/**\n * This function tells why the model prefers one hypothesis to a second hypothesis.\n *\n * It gives each observation with its weight, from the largest weight in absolute value. The default second hypothesis is\n * \"no fault\".\n */\nexport function explain(catalog: Catalog, evidence: readonly Evidence[], hypothesis: string, versus: string = NONE): Contribution[] {\n const { used } = usable(catalog, evidence);\n return used\n .map((item) => ({\n evidence: item,\n weight: Math.log10(catalog.likelihood(hypothesis, item.feature, item.value, item.primary)) -\n Math.log10(catalog.likelihood(versus, item.feature, item.value, item.primary)),\n }))\n .sort((a, b) => Math.abs(b.weight) - Math.abs(a.weight));\n}\n\n/**\n * This function gives the probability of each value of a feature that is not observed yet, from the probabilities of\n * the causes. The choice of the next probe uses it.\n */\nexport function predictive(catalog: Catalog, diagnosis: Diagnosis, feature: string, primary: boolean): Distribution {\n const known = catalog.features.get(feature);\n if (known === undefined) throw new RangeError(`The catalog has no feature ${feature}`);\n const dist: Record<string, number> = {};\n for (const value of known.values) {\n dist[value] = diagnosis.causes.reduce((sum, cause) => sum + cause.probability * catalog.likelihood(cause.id, feature, value, primary), 0);\n }\n return dist;\n}\n","import { compileCatalog, type Catalog, type CatalogSpec, type Feature, type Fault } from \"./model.ts\";\n\n/**\n * The features of the standard catalog.\n *\n * The readers of the facts give these features. The distribution `healthy` of each feature is a rule of practice. The\n * lab runs (ND-7) then fit it.\n */\nexport const STANDARD_FEATURES: readonly Feature[] = [\n {\n id: \"link.state\",\n layer: \"link\",\n summary: \"The state of the link of the network interface.\",\n values: [\"up\", \"down\"],\n healthy: { up: 0.999, down: 0.001 },\n },\n {\n id: \"wifi.signal\",\n layer: \"wifi\",\n summary: \"The strength of the Wi-Fi signal: strong above -60 dBm, weak below -75 dBm.\",\n values: [\"strong\", \"fair\", \"weak\"],\n healthy: { strong: 0.7, fair: 0.25, weak: 0.05 },\n },\n {\n id: \"gateway.reply\",\n layer: \"route\",\n summary: \"The gateway answers an ICMP echo request.\",\n values: [\"reply\", \"none\"],\n healthy: { reply: 0.97, none: 0.03 },\n },\n {\n id: \"path.loss\",\n layer: \"path\",\n summary: \"The packet loss on the path: none, some (up to 5 %) or high (more than 5 %).\",\n values: [\"none\", \"some\", \"high\"],\n healthy: { none: 0.9, some: 0.08, high: 0.02 },\n },\n {\n id: \"dns.answer\",\n layer: \"dns\",\n summary: \"The result of the DNS lookup of the name of the target.\",\n values: [\"ok\", \"nxdomain\", \"servfail\", \"refused\", \"timeout\"],\n healthy: { ok: 0.97, nxdomain: 0.01, servfail: 0.005, refused: 0.005, timeout: 0.01 },\n },\n {\n id: \"dns.address\",\n layer: \"dns\",\n summary: \"The kind of the addresses of a public name: public, or private (RFC 1918, RFC 4193 or loopback).\",\n values: [\"public\", \"private\"],\n healthy: { public: 0.95, private: 0.05 },\n },\n {\n id: \"ip.family\",\n layer: \"ip\",\n summary: \"The address families that connect, for a host with IPv4 and IPv6 addresses.\",\n values: [\"both\", \"ipv4-only\", \"ipv6-only\"],\n healthy: { both: 0.9, \"ipv4-only\": 0.08, \"ipv6-only\": 0.02 },\n },\n {\n id: \"icmp.reply\",\n layer: \"ip\",\n summary: \"The host answers an ICMP echo request, does not answer, or a router tells that it is unreachable.\",\n values: [\"reply\", \"none\", \"unreachable\"],\n healthy: { reply: 0.85, none: 0.14, unreachable: 0.01 },\n },\n {\n id: \"tcp.connect\",\n layer: \"tcp\",\n summary: \"The outcome of a TCP connect to the port of the target.\",\n values: [\"connected\", \"refused\", \"timeout\", \"unreachable\", \"reset\"],\n healthy: { connected: 0.97, refused: 0.01, timeout: 0.01, unreachable: 0.005, reset: 0.005 },\n },\n {\n id: \"tcp.time\",\n layer: \"tcp\",\n summary: \"The time of the TCP connect: short (below 150 ms), long, or retry (900 ms or more, a SYN sent again).\",\n values: [\"short\", \"long\", \"retry\"],\n healthy: { short: 0.8, long: 0.18, retry: 0.02 },\n },\n {\n id: \"tls.handshake\",\n layer: \"tls\",\n summary: \"The outcome of the TLS handshake.\",\n values: [\"ok\", \"cert-error\", \"alert\", \"timeout\", \"reset\"],\n healthy: { ok: 0.97, \"cert-error\": 0.01, alert: 0.01, timeout: 0.005, reset: 0.005 },\n },\n {\n id: \"tls.ratio\",\n layer: \"tls\",\n summary: \"The time of the TLS handshake divided by the time of the TCP connect: low (below 3), mid, or high (above 8).\",\n values: [\"low\", \"mid\", \"high\"],\n healthy: { low: 0.75, mid: 0.2, high: 0.05 },\n },\n {\n id: \"tls.trust\",\n layer: \"tls\",\n summary: \"The issuer of the certificate: a public root of trust, or a private root.\",\n values: [\"public\", \"private\"],\n healthy: { public: 0.97, private: 0.03 },\n },\n {\n id: \"http.status\",\n layer: \"http\",\n summary: \"The class of the HTTP status: ok (2xx and 3xx), client-error (4xx), server-error (5xx), or none.\",\n values: [\"ok\", \"client-error\", \"server-error\", \"none\"],\n healthy: { ok: 0.95, \"client-error\": 0.03, \"server-error\": 0.01, none: 0.01 },\n },\n {\n id: \"http.ttfb\",\n layer: \"http\",\n summary: \"The time to the first byte divided by the time of the TCP connect: low (below 4), mid, or high (above 20).\",\n values: [\"low\", \"mid\", \"high\"],\n healthy: { low: 0.6, mid: 0.3, high: 0.1 },\n },\n];\n\n/**\n * The faults of the standard catalog. Each prediction is a rule of practice. The lab runs (ND-7) then fit the\n * distributions, and the priors.\n */\nexport const STANDARD_FAULTS: readonly Fault[] = [\n {\n id: \"interface.down\",\n part: \"interface\",\n reach: \"shared\",\n summary: \"The network interface has no link.\",\n step: \"Connect the cable or the Wi-Fi network, and make sure that the interface is on.\",\n predicts: {\n \"link.state\": { expect: \"down\", strength: \"sure\" },\n \"gateway.reply\": { expect: \"none\", strength: \"sure\" },\n \"dns.answer\": { expect: \"timeout\", strength: \"strong\" },\n \"icmp.reply\": { expect: [\"none\", \"unreachable\"], strength: \"sure\" },\n \"tcp.connect\": { expect: [\"unreachable\", \"timeout\"], strength: \"sure\" },\n },\n },\n {\n id: \"wifi.weak\",\n part: \"wifi\",\n reach: \"shared\",\n summary: \"The Wi-Fi signal is weak, so the link loses packets.\",\n step: \"Move nearer to the access point, or use a cable.\",\n predicts: {\n \"wifi.signal\": { expect: \"weak\", strength: \"strong\" },\n \"path.loss\": { expect: [\"some\", \"high\"], strength: \"usual\" },\n \"tcp.time\": { expect: [\"long\", \"retry\"], strength: \"usual\" },\n },\n },\n {\n id: \"gateway.down\",\n part: \"gateway\",\n reach: \"shared\",\n summary: \"The gateway does not forward packets.\",\n step: \"Restart the router, or examine the link from the router to the provider.\",\n predicts: {\n \"gateway.reply\": { expect: \"none\", strength: \"strong\" },\n \"dns.answer\": { expect: \"timeout\", strength: \"strong\" },\n \"icmp.reply\": { expect: [\"none\", \"unreachable\"], strength: \"strong\" },\n \"tcp.connect\": { expect: [\"timeout\", \"unreachable\"], strength: \"strong\" },\n },\n },\n {\n id: \"captive.portal\",\n part: \"gateway\",\n reach: \"shared\",\n summary: \"A captive portal holds the traffic until you sign in.\",\n step: \"Open a web page in a browser, and accept the terms of the network.\",\n predicts: {\n \"dns.address\": { expect: \"private\", strength: \"usual\" },\n \"tls.handshake\": { expect: \"cert-error\", strength: \"strong\" },\n \"tls.trust\": { expect: \"private\", strength: \"usual\" },\n },\n },\n {\n id: \"resolver.down\",\n part: \"resolver\",\n reach: \"shared\",\n summary: \"The DNS resolver does not answer.\",\n step: \"Use a different DNS resolver, or examine the resolver of the router.\",\n predicts: {\n \"dns.answer\": { expect: \"timeout\", strength: \"strong\" },\n },\n },\n {\n id: \"resolver.failing\",\n part: \"resolver\",\n reach: \"shared\",\n summary: \"The DNS resolver answers with an error for each name.\",\n step: \"Use a different DNS resolver, and examine the DNSSEC settings of the resolver.\",\n predicts: {\n \"dns.answer\": { expect: [\"servfail\", \"refused\"], strength: \"strong\" },\n },\n },\n {\n id: \"name.missing\",\n part: \"service\",\n reach: \"target\",\n summary: \"The name does not exist in DNS.\",\n step: \"Examine the spelling of the name, and the records of its DNS zone.\",\n predicts: {\n \"dns.answer\": { expect: \"nxdomain\", strength: \"strong\" },\n },\n },\n {\n id: \"port.closed\",\n part: \"service\",\n reach: \"target\",\n summary: \"No program listens on the port.\",\n step: \"Start the service, or use the correct port.\",\n predicts: {\n \"tcp.connect\": { expect: \"refused\", strength: \"strong\" },\n },\n },\n {\n id: \"host.down\",\n part: \"service\",\n reach: \"target\",\n summary: \"The host is down.\",\n step: \"Start the host, or examine its network link.\",\n predicts: {\n \"tcp.connect\": { expect: [\"timeout\", \"unreachable\"], strength: \"strong\" },\n \"icmp.reply\": { expect: [\"none\", \"unreachable\"], strength: \"strong\" },\n },\n },\n {\n id: \"filter.drop\",\n part: \"path\",\n reach: \"target\",\n summary: \"A filter drops the packets to the port.\",\n step: \"Examine the firewall rules of the host and of each network on the path.\",\n predicts: {\n \"tcp.connect\": { expect: \"timeout\", strength: \"strong\" },\n \"icmp.reply\": { reply: 0.5, none: 0.45, unreachable: 0.05 },\n },\n },\n {\n id: \"filter.icmp\",\n part: \"path\",\n reach: \"target\",\n summary: \"A filter blocks ICMP. The host is up.\",\n step: \"No step is necessary for the service. Use TCP to examine the host.\",\n predicts: {\n \"icmp.reply\": { expect: \"none\", strength: \"strong\" },\n },\n },\n {\n id: \"filter.reset\",\n part: \"path\",\n reach: \"target\",\n summary: \"A device on the path resets the connections.\",\n step: \"Examine the firewalls and the filters of content on the path, and try a different network.\",\n predicts: {\n \"tcp.connect\": { connected: 0.5, refused: 0.02, timeout: 0.02, unreachable: 0.02, reset: 0.44 },\n \"tls.handshake\": { expect: \"reset\", strength: \"strong\" },\n },\n },\n {\n id: \"path.loss\",\n part: \"path\",\n reach: \"target\",\n summary: \"A network on the path loses packets.\",\n step: \"Find the first hop with loss, and tell the provider of that network.\",\n predicts: {\n \"path.loss\": { expect: [\"some\", \"high\"], strength: \"strong\" },\n \"tcp.time\": { expect: [\"long\", \"retry\"], strength: \"usual\" },\n \"http.ttfb\": { expect: [\"mid\", \"high\"], strength: \"weak\" },\n },\n },\n {\n id: \"path.mtu\",\n part: \"path\",\n reach: \"shared\",\n summary: \"Large packets do not pass, and no ICMP message tells the size (an MTU black hole).\",\n step: \"Lower the MTU of the interface or of the VPN, or enable MSS clamping on the router.\",\n predicts: {\n \"tls.handshake\": { expect: \"timeout\", strength: \"strong\" },\n \"http.status\": { expect: \"none\", strength: \"usual\" },\n },\n },\n {\n id: \"ipv6.broken\",\n part: \"path\",\n reach: \"shared\",\n summary: \"IPv6 does not work, and IPv4 works.\",\n step: \"Examine the IPv6 settings of the router, or turn off IPv6 until the provider repairs it.\",\n predicts: {\n \"ip.family\": { expect: \"ipv4-only\", strength: \"strong\" },\n \"tcp.time\": { expect: [\"long\", \"retry\"], strength: \"weak\" },\n },\n },\n {\n id: \"tls.proxy\",\n part: \"path\",\n reach: \"shared\",\n summary: \"A proxy examines the TLS traffic.\",\n step: \"Compare the issuer of the certificate with the issuer on a different network, and ask the owner of the network.\",\n predicts: {\n \"tls.ratio\": { expect: [\"mid\", \"high\"], strength: \"usual\" },\n \"tls.trust\": { expect: \"private\", strength: \"usual\" },\n \"tcp.time\": { expect: \"short\", strength: \"strong\" },\n },\n },\n {\n id: \"tls.cert-invalid\",\n part: \"service\",\n reach: \"target\",\n summary: \"The certificate of the server is not valid: it expired, or it is for a different name.\",\n step: \"Renew the certificate of the server, or correct its names.\",\n predicts: {\n \"tls.handshake\": { expect: \"cert-error\", strength: \"strong\" },\n },\n },\n {\n id: \"tls.mismatch\",\n part: \"service\",\n reach: \"target\",\n summary: \"The client and the server have no TLS version or cipher in common.\",\n step: \"Enable TLS 1.2 or TLS 1.3 with modern ciphers on the server.\",\n predicts: {\n \"tls.handshake\": { expect: \"alert\", strength: \"strong\" },\n },\n },\n {\n id: \"server.slow\",\n part: \"service\",\n reach: \"target\",\n summary: \"The server is slow.\",\n step: \"Examine the load and the logs of the server.\",\n predicts: {\n \"http.ttfb\": { expect: \"high\", strength: \"strong\" },\n \"tls.ratio\": { expect: [\"mid\", \"high\"], strength: \"weak\" },\n },\n },\n {\n id: \"server.error\",\n part: \"service\",\n reach: \"target\",\n summary: \"The server answers with an error.\",\n step: \"Examine the logs of the server and of its upstream services.\",\n predicts: {\n \"http.status\": { expect: \"server-error\", strength: \"strong\" },\n },\n },\n];\n\n/** The standard catalog: the rules of practice as the first likelihoods. Each hypothesis has the prior weight 1. */\nexport const STANDARD_CATALOG_SPEC: CatalogSpec = {\n version: \"1\",\n features: STANDARD_FEATURES,\n faults: STANDARD_FAULTS,\n};\n\n/** The standard catalog, compiled. */\nexport const STANDARD_CATALOG: Catalog = (() => {\n const compiled = compileCatalog(STANDARD_CATALOG_SPEC);\n if (compiled.tag === \"err\") throw new Error(`The standard catalog has problems: ${compiled.error.join(\" \")}`);\n return compiled.value;\n})();\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-CP1hzELI.d.ts","names":[],"sources":["../src/layer.ts","../src/diagnosis/model.ts","../src/diagnosis/catalog.ts"],"mappings":";;;;;;;;;;;;;;cAaa;;KAGD,gBAAgB;;iBAKZ,QAAQ,iBAAiB,SAAS;;iBAKlC,UAAU,OAAO;;KCtBrB,eAAe,SAAS;;;;;;UAOnB;WACN;WACA,OAAO;WACP;WACA;WACA,SAAS;;;cAIP;;KAGD,eAAe;;;;;;;KAQf;;;;;KAMA;;cAGC,WAAW,SAAS,OAAO;;;;;;KAO5B,aAAa,eAAe;;UAGvB;WACN;WACA,UAAU;;;;;;;;UAaJ;WACN;WACA,MAAM;WACN,OAAO;;WAEP;;WAEA;WACA;WACA,UAAU,SAAS,eAAe;;;UAI5B;WACN;WACA,mBAAmB;WACnB,iBAAiB;WACjB;aAAoB;aAAoC;;;;;;;;;;UAUlD;WACN;WACA;WACA;WACA;WACA,OAAO;WACP,QAAQ;;;cAIN;;cAEA;;UAGI;WACN;WACA,UAAU,oBAAoB;WAC9B,qBAAqB;;EAE9B,WAAW,oBAAoB,iBAAiB,eAAe;EAC/D,MAAM;;;cAIK;;iBAkBG,OAAO,SAAS,SAAS,YAAY,aAAa,OAAO;;;;;;iBAqBzD,eAAe,MAAM,cAAc,OAAO;;;;;;;UAwEzC;WACN;WACA;WACA;WACA;;;UAIM,cAAc;WACpB;;;UAIM;WACN,iBAAiB;WACjB,eAAe;WACf;aAA6B,UAAU;aAAmB;;;;;;;;;;iBAsBrD,SAAS,SAAS,SAAS,mBAAmB,aAAa;;UAoB1D;WACN,UAAU;;WAEV;;;;;;;;iBASK,QAAQ,SAAS,SAAS,mBAAmB,YAAY,oBAAoB,kBAAwB;;;;;iBAerG,WAAW,SAAS,SAAS,WAAW,WAAW,iBAAiB,mBAAmB;;;;;;;cCjT1F,4BAA4B;;;;;cAgH5B,0BAA0B;;cAiO1B,uBAAuB;;cAOvB,kBAAkB"}