@mark1russell7/rigor-chaos 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @mark1russell7/rigor-chaos
2
2
 
3
- This package holds open-loop load schedules, and the measure of latency that goes with them.
3
+ This package holds open-loop load schedules, the measure of latency that goes with them, and seeded fault plans for a proxy.
4
4
 
5
5
  A closed-loop driver waits for each reply before its next request. A stall of the target thus stops the load, and the measure shows one slow request. This is coordinated omission. An open-loop schedule fixes each arrival time before the run, and a measure counts each latency from its planned time.
6
6
 
@@ -33,6 +33,33 @@ A schedule is data. A driver reads `times`, and it sends each arrival at its tim
33
33
 
34
34
  `scheduleHeld` tells if the driver kept the schedule. A run that fails this check measures its driver, not its target. An A/A run with no fault examines this first. When a run fails this check, `seconds` tells if a full processor or a held event loop made the sends late.
35
35
 
36
+ ## A fault plan
37
+
38
+ A fault plan tells a proxy which faults to put into which connections, and at which points. A seed fixes the plan before the run. The proxy that applies a plan is `@mark1russell7/rigor-proxy`.
39
+
40
+ | Kind | What the proxy does |
41
+ |---|---|
42
+ | `delay` | Each frame that it reads in the next `forMs` goes out `ms` later. It does not stop its reads. |
43
+ | `stall` | It stops reading the direction for `ms`. The sender sees back pressure. |
44
+ | `cut` | It ends both directions with a FIN. |
45
+ | `reset` | It destroys both directions with a TCP reset. |
46
+ | `split` | The frame goes out in `pieces` writes, with `gapMs` between two writes. |
47
+ | `reorder` | The next `count` frames go out in the reverse order. After `holdMs`, it sends the frames that it holds. |
48
+ | `duplicate` | The frame goes out `1 + copies` times. |
49
+
50
+ Each fault names its connection (the number of the connection, from 0), its direction (`up` to the server, `down` to the client) and its point. A point is a time in ms from the start of the connection, a count of bytes, or a count of frames.
51
+
52
+ ```ts
53
+ import { faultPlan } from "@mark1russell7/rigor-chaos";
54
+
55
+ const plan = faultPlan({ connections: 20, perConnection: 3, kinds: { split: 2, delay: 1, reset: 1 }, within: { frame: 50 } }, "drill");
56
+ ```
57
+
58
+ - Each connection draws from its own stream of the seed. Thus a spec with more connections keeps the faults of the first connections.
59
+ - Each draw is an integer, thus the same seed gives the same plan on each platform.
60
+ - `ranges` sets the interval of each parameter. `DEFAULT_FAULT_RANGES` gives the intervals that a spec does not set.
61
+ - `faultPlanOf` makes a plan from faults that a person wrote. `checkFaultPlan` gives each problem of a plan from a record.
62
+
36
63
  ## A closed-loop record
37
64
 
38
65
  `closedLoopPlan` reads a run of a closed-loop driver that sends each `interval` ms. It puts back the sends that each stall hid, by the rule of `recordValueWithExpectedInterval` of HdrHistogram.
@@ -0,0 +1,130 @@
1
+ /**
2
+ * A fault plan tells a proxy which faults to put into which connections, and at which points. A seed fixes the plan
3
+ * before the run, as it fixes a schedule. Thus a record keeps the plan, and a later run applies the same faults.
4
+ *
5
+ * The plan is data, and this module has no I/O. The proxy that applies a plan is the package `@mark1russell7/rigor-proxy`.
6
+ * Each draw of a plan is an integer from `mulberry32`, thus the same seed gives the same plan on each platform.
7
+ */
8
+ /** This value is the format of a fault plan. */
9
+ export declare const FAULT_PLAN_FORMAT = "rigor-faults/1";
10
+ /** These are the kinds of faults, in the order that a weight list of a spec uses. */
11
+ export declare const FAULT_KINDS: readonly ["delay", "stall", "cut", "reset", "split", "reorder", "duplicate"];
12
+ /** A kind of fault. */
13
+ export type FaultKind = (typeof FAULT_KINDS)[number];
14
+ /** The direction of a stream. `up` goes from the client to the server, and `down` goes from the server to the client. */
15
+ export type Direction = "up" | "down";
16
+ /**
17
+ * The point of a fault in the stream of its direction. `ms` counts from the start of the connection. `byte` counts the
18
+ * bytes of the direction before the fault. `frame` counts the frames of the direction before the fault. The framer of
19
+ * the proxy sets what a frame is: a WebSocket frame, a part of an HTTP message or a TCP read.
20
+ */
21
+ export type Trigger = {
22
+ readonly ms: number;
23
+ } | {
24
+ readonly byte: number;
25
+ } | {
26
+ readonly frame: number;
27
+ };
28
+ /** These fields tell where a fault occurs. Connection `i` is the connection that the proxy accepts as number `i`, from 0. */
29
+ export interface FaultPoint {
30
+ readonly connection: number;
31
+ readonly direction: Direction;
32
+ readonly at: Trigger;
33
+ }
34
+ /**
35
+ * One fault of a plan.
36
+ *
37
+ * - `delay`: each frame that the proxy reads in the next `forMs` goes out `ms` later. The proxy does not stop its reads.
38
+ * - `stall`: the proxy stops reading the direction for `ms`. Thus the sender sees back pressure.
39
+ * - `cut`: the proxy ends both directions with a FIN, as a clean close of TCP.
40
+ * - `reset`: the proxy destroys both directions with a TCP reset (RST).
41
+ * - `split`: the frame goes out in `pieces` writes, with `gapMs` between two writes.
42
+ * - `reorder`: the next `count` frames go out in the reverse order. After `holdMs`, the proxy sends the frames that it holds.
43
+ * - `duplicate`: the frame goes out `1 + copies` times.
44
+ */
45
+ export type Fault = FaultPoint & ({
46
+ readonly kind: "delay";
47
+ readonly ms: number;
48
+ readonly forMs: number;
49
+ } | {
50
+ readonly kind: "stall";
51
+ readonly ms: number;
52
+ } | {
53
+ readonly kind: "cut";
54
+ } | {
55
+ readonly kind: "reset";
56
+ } | {
57
+ readonly kind: "split";
58
+ readonly pieces: number;
59
+ readonly gapMs: number;
60
+ } | {
61
+ readonly kind: "reorder";
62
+ readonly count: number;
63
+ readonly holdMs: number;
64
+ } | {
65
+ readonly kind: "duplicate";
66
+ readonly copies: number;
67
+ });
68
+ /** A fault plan is data, thus a record can keep the plan that a proxy applied. */
69
+ export interface FaultPlan {
70
+ readonly format: typeof FAULT_PLAN_FORMAT;
71
+ /** The seed of a plan from a spec, or `null` for a plan that a person wrote. */
72
+ readonly seed: number | string | null;
73
+ /** The faults, in the order of their connections. In a plan from a spec, the faults of a connection are in the order of their points. */
74
+ readonly faults: readonly Fault[];
75
+ }
76
+ /** An interval of whole numbers. Both ends are included, and the low end comes first. */
77
+ export type Interval = readonly [number, number];
78
+ /** These intervals hold the parameters of the faults of a spec. */
79
+ export interface FaultRanges {
80
+ readonly delayMs: Interval;
81
+ readonly delayForMs: Interval;
82
+ readonly stallMs: Interval;
83
+ readonly splitPieces: Interval;
84
+ readonly splitGapMs: Interval;
85
+ readonly reorderCount: Interval;
86
+ readonly reorderHoldMs: Interval;
87
+ readonly duplicateCopies: Interval;
88
+ }
89
+ /** These intervals are the default parameters of a spec. */
90
+ export declare const DEFAULT_FAULT_RANGES: FaultRanges;
91
+ /** A spec tells `faultPlan` how many faults to draw, of which kinds, and in which part of each stream. */
92
+ export interface FaultSpec {
93
+ /** The number of connections that the plan covers. */
94
+ readonly connections: number;
95
+ /** The number of faults of each connection. */
96
+ readonly perConnection: number;
97
+ /** The weight of each kind. A kind without a weight, or with the weight 0, does not occur. */
98
+ readonly kinds: Partial<Readonly<Record<FaultKind, number>>>;
99
+ /** Each point is a whole number from 0 to less than this limit, in the measure of the limit. */
100
+ readonly within: Trigger;
101
+ /** The directions to draw from. The default is both directions. */
102
+ readonly directions?: readonly Direction[];
103
+ /** The intervals of the parameters. Each interval that the spec does not give comes from `DEFAULT_FAULT_RANGES`. */
104
+ readonly ranges?: Partial<FaultRanges>;
105
+ }
106
+ /** This function gives the measure and the value of a point, or `undefined` when the point is not valid. */
107
+ export declare function triggerOf(at: Trigger): {
108
+ readonly measure: "ms" | "byte" | "frame";
109
+ readonly value: number;
110
+ } | undefined;
111
+ /** This function gives each problem of one fault. An empty list means that the fault is valid. */
112
+ export declare function checkFault(fault: Fault): readonly string[];
113
+ /** This function gives each problem of a plan. An empty list means that the plan is valid. */
114
+ export declare function checkFaultPlan(plan: FaultPlan): readonly string[];
115
+ /**
116
+ * This function makes a plan from faults that a person wrote. The faults go into the order of their connections, and the
117
+ * faults of one connection keep their order. This function throws a `RangeError` for a fault with problems.
118
+ */
119
+ export declare function faultPlanOf(faults: readonly Fault[]): FaultPlan;
120
+ /** This function gives the faults of one connection, in the order of the plan. */
121
+ export declare function faultsOf(plan: FaultPlan, connection: number): readonly Fault[];
122
+ /** This function gives each problem of a spec. An empty list means that the spec is valid. */
123
+ export declare function checkFaultSpec(spec: FaultSpec): readonly string[];
124
+ /**
125
+ * This function draws a fault plan from a spec and a seed. Each connection draws from its own stream, `fork` of the seed.
126
+ * Thus a spec with more connections keeps the faults of the first connections. The faults of a connection are in the
127
+ * order of their points. This function throws a `RangeError` for a spec with problems.
128
+ */
129
+ export declare function faultPlan(spec: FaultSpec, seed: number | string): FaultPlan;
130
+ //# sourceMappingURL=faults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"faults.d.ts","names":[],"sources":["../src/faults.ts"],"mappings":";;;;;;;;qBAWa;;qBAGA;;YAGD,oBAAoB;;YAGpB;;;;;;YAOA;WAAqB;;WAA0B;;WAA4B;;;iBAGtE;WACN;WACA,WAAW;WACX,IAAI;;;;;;;;;;;;;YAcH,QAAQ;WAEH;WAAwB;WAAqB;;WAC7C;WAAwB;;WACxB;;WACA;;WACA;WAAwB;WAAyB;;WACjD;WAA0B;WAAwB;;WAClD;WAA4B;;;iBAI5B;WACN,eAAe;;WAEf;;WAEA,iBAAiB;;;YAIhB;;iBAGK;WACN,SAAS;WACT,YAAY;WACZ,SAAS;WACT,aAAa;WACb,YAAY;WACZ,cAAc;WACd,eAAe;WACf,iBAAiB;;;qBAIf,sBAAsB;;iBAYlB;;WAEN;;WAEA;;WAEA,OAAO,QAAQ,SAAS,OAAO;;WAE/B,QAAQ;;WAER,sBAAsB;;WAEtB,SAAS,QAAQ;;;wBAOZ,UAAU,IAAI;WAAqB;WAA2C;;;wBA+B9E,WAAW,OAAO;;wBAkBlB,eAAe,MAAM;;;;;wBAcrB,YAAY,iBAAiB,UAAU;;wBAQvC,SAAS,MAAM,WAAW,8BAA8B;;wBAiBxD,eAAe,MAAM;;;;;;wBAoCrB,UAAU,MAAM,WAAW,wBAAwB"}
@@ -0,0 +1,245 @@
1
+ import { random } from "@mark1russell7/rigor-generators";
2
+ // src/faults.ts
3
+ /**
4
+ * A fault plan tells a proxy which faults to put into which connections, and at which points. A seed fixes the plan
5
+ * before the run, as it fixes a schedule. Thus a record keeps the plan, and a later run applies the same faults.
6
+ *
7
+ * The plan is data, and this module has no I/O. The proxy that applies a plan is the package `@mark1russell7/rigor-proxy`.
8
+ * Each draw of a plan is an integer from `mulberry32`, thus the same seed gives the same plan on each platform.
9
+ */
10
+ /** This value is the format of a fault plan. */
11
+ const FAULT_PLAN_FORMAT = "rigor-faults/1";
12
+ /** These are the kinds of faults, in the order that a weight list of a spec uses. */
13
+ const FAULT_KINDS = [
14
+ "delay",
15
+ "stall",
16
+ "cut",
17
+ "reset",
18
+ "split",
19
+ "reorder",
20
+ "duplicate"
21
+ ];
22
+ /** These intervals are the default parameters of a spec. */
23
+ const DEFAULT_FAULT_RANGES = {
24
+ delayMs: [50, 2e3],
25
+ delayForMs: [100, 5e3],
26
+ stallMs: [100, 1e4],
27
+ splitPieces: [2, 8],
28
+ splitGapMs: [0, 50],
29
+ reorderCount: [2, 4],
30
+ reorderHoldMs: [100, 1e3],
31
+ duplicateCopies: [1, 2]
32
+ };
33
+ const isWhole = (value, least) => typeof value === "number" && Number.isSafeInteger(value) && value >= least;
34
+ const isTime = (value, least) => typeof value === "number" && Number.isFinite(value) && value >= least;
35
+ /** This function gives the measure and the value of a point, or `undefined` when the point is not valid. */
36
+ function triggerOf(at) {
37
+ if (typeof at !== "object" || at === null) return void 0;
38
+ const keys = Object.keys(at);
39
+ if (keys.length !== 1) return void 0;
40
+ const measure = keys[0];
41
+ const value = at[measure];
42
+ if (measure === "ms" && isTime(value, 0)) return {
43
+ measure,
44
+ value
45
+ };
46
+ if ((measure === "byte" || measure === "frame") && isWhole(value, 0)) return {
47
+ measure,
48
+ value
49
+ };
50
+ }
51
+ const PARAMETERS = {
52
+ delay: [[
53
+ "ms",
54
+ "time",
55
+ 1
56
+ ], [
57
+ "forMs",
58
+ "time",
59
+ 1
60
+ ]],
61
+ stall: [[
62
+ "ms",
63
+ "time",
64
+ 1
65
+ ]],
66
+ cut: [],
67
+ reset: [],
68
+ split: [[
69
+ "pieces",
70
+ "whole",
71
+ 2
72
+ ], [
73
+ "gapMs",
74
+ "time",
75
+ 0
76
+ ]],
77
+ reorder: [[
78
+ "count",
79
+ "whole",
80
+ 2
81
+ ], [
82
+ "holdMs",
83
+ "time",
84
+ 1
85
+ ]],
86
+ duplicate: [[
87
+ "copies",
88
+ "whole",
89
+ 1
90
+ ]]
91
+ };
92
+ /** This function gives each problem of one fault. An empty list means that the fault is valid. */
93
+ function checkFault(fault) {
94
+ const out = [];
95
+ if (!isWhole(fault.connection, 0)) out.push(`The connection is a whole number of 0 or more, not ${String(fault.connection)}.`);
96
+ if (fault.direction !== "up" && fault.direction !== "down") out.push(`The direction is up or down, not ${String(fault.direction)}.`);
97
+ if (triggerOf(fault.at) === void 0) out.push(`The point has one key of ms, byte or frame, with a value of 0 or more, not ${JSON.stringify(fault.at)}.`);
98
+ if (!FAULT_KINDS.includes(fault.kind)) {
99
+ out.push(`The kind is one of ${FAULT_KINDS.join(", ")}, not ${String(fault.kind)}.`);
100
+ return out;
101
+ }
102
+ for (const [name, type, least] of PARAMETERS[fault.kind]) {
103
+ const value = fault[name];
104
+ if (!(type === "whole" ? isWhole(value, least) : isTime(value, least))) out.push(`The ${fault.kind} field ${name} is a ${type === "whole" ? "whole number" : "time"} of ${least} or more, not ${String(value)}.`);
105
+ }
106
+ return out;
107
+ }
108
+ /** This function gives each problem of a plan. An empty list means that the plan is valid. */
109
+ function checkFaultPlan(plan) {
110
+ const out = [];
111
+ if (plan.format !== "rigor-faults/1") out.push(`The format is ${FAULT_PLAN_FORMAT}, not ${String(plan.format)}.`);
112
+ if (!Array.isArray(plan.faults)) return [...out, "The faults are a list."];
113
+ plan.faults.forEach((fault, index) => {
114
+ for (const problem of checkFault(fault)) out.push(`The fault ${index}: ${problem}`);
115
+ });
116
+ return out;
117
+ }
118
+ /**
119
+ * This function makes a plan from faults that a person wrote. The faults go into the order of their connections, and the
120
+ * faults of one connection keep their order. This function throws a `RangeError` for a fault with problems.
121
+ */
122
+ function faultPlanOf(faults) {
123
+ const plan = {
124
+ format: FAULT_PLAN_FORMAT,
125
+ seed: null,
126
+ faults: [...faults].sort((a, b) => a.connection - b.connection)
127
+ };
128
+ const problems = checkFaultPlan(plan);
129
+ if (problems.length > 0) throw new RangeError(`faultPlanOf: ${problems.join(" ")}`);
130
+ return plan;
131
+ }
132
+ /** This function gives the faults of one connection, in the order of the plan. */
133
+ function faultsOf(plan, connection) {
134
+ return plan.faults.filter((fault) => fault.connection === connection);
135
+ }
136
+ /** The least value of each interval. A split has 2 pieces or more, and a reorder has 2 frames or more. */
137
+ const RANGE_LEAST = {
138
+ delayMs: 1,
139
+ delayForMs: 1,
140
+ stallMs: 1,
141
+ splitPieces: 2,
142
+ splitGapMs: 0,
143
+ reorderCount: 2,
144
+ reorderHoldMs: 1,
145
+ duplicateCopies: 1
146
+ };
147
+ /** This function gives each problem of a spec. An empty list means that the spec is valid. */
148
+ function checkFaultSpec(spec) {
149
+ const out = [];
150
+ if (!isWhole(spec.connections, 1)) out.push(`The connections are a whole number of 1 or more, not ${String(spec.connections)}.`);
151
+ if (!isWhole(spec.perConnection, 0)) out.push(`The faults of each connection are a whole number of 0 or more, not ${String(spec.perConnection)}.`);
152
+ const weights = Object.entries(spec.kinds ?? {});
153
+ for (const [kind, weight] of weights) if (!FAULT_KINDS.includes(kind)) out.push(`The kind ${kind} is not a kind of fault.`);
154
+ else if (!isTime(weight, 0)) out.push(`The weight of ${kind} is a number of 0 or more, not ${String(weight)}.`);
155
+ if (!weights.filter(([kind]) => FAULT_KINDS.includes(kind)).some(([, weight]) => typeof weight === "number" && weight > 0)) out.push("At least one kind of fault has a weight more than 0.");
156
+ const within = triggerOf(spec.within);
157
+ if (within === void 0 || !Number.isSafeInteger(within.value) || within.value < 1) out.push(`The limit of the points has one key of ms, byte or frame, with a whole value of 1 or more, not ${JSON.stringify(spec.within)}.`);
158
+ const directions = spec.directions ?? ["up", "down"];
159
+ if (directions.length === 0 || directions.some((d) => d !== "up" && d !== "down")) out.push(`The directions are up or down, not ${JSON.stringify(spec.directions)}.`);
160
+ for (const [name, interval] of Object.entries(spec.ranges ?? {})) {
161
+ if (!Object.hasOwn(RANGE_LEAST, name)) {
162
+ out.push(`The range ${name} is not a range of a fault parameter.`);
163
+ continue;
164
+ }
165
+ const [low, high] = Array.isArray(interval) ? interval : [];
166
+ const least = RANGE_LEAST[name];
167
+ if (!isWhole(low, least) || !isWhole(high, least) || high < low) out.push(`The range ${name} has two whole numbers of ${least} or more, the low one first, not ${JSON.stringify(interval)}.`);
168
+ }
169
+ return out;
170
+ }
171
+ /**
172
+ * This function draws a fault plan from a spec and a seed. Each connection draws from its own stream, `fork` of the seed.
173
+ * Thus a spec with more connections keeps the faults of the first connections. The faults of a connection are in the
174
+ * order of their points. This function throws a `RangeError` for a spec with problems.
175
+ */
176
+ function faultPlan(spec, seed) {
177
+ const problems = checkFaultSpec(spec);
178
+ if (problems.length > 0) throw new RangeError(`faultPlan: ${problems.join(" ")}`);
179
+ const ranges = {
180
+ ...DEFAULT_FAULT_RANGES,
181
+ ...spec.ranges
182
+ };
183
+ const kinds = FAULT_KINDS.filter((kind) => (spec.kinds[kind] ?? 0) > 0);
184
+ const weights = kinds.map((kind) => spec.kinds[kind]);
185
+ const directions = spec.directions ?? ["up", "down"];
186
+ const within = triggerOf(spec.within);
187
+ const root = random(seed);
188
+ const faults = [];
189
+ for (let connection = 0; connection < spec.connections; connection += 1) {
190
+ const rng = root.fork(`connection ${connection}`);
191
+ const draw = (interval) => rng.int(interval[0], interval[1]);
192
+ const drawn = [];
193
+ for (let index = 0; index < spec.perConnection; index += 1) {
194
+ const kind = kinds[rng.weighted(weights)];
195
+ const direction = rng.pick(directions);
196
+ const point = rng.int(0, within.value - 1);
197
+ const base = {
198
+ connection,
199
+ direction,
200
+ at: { [within.measure]: point }
201
+ };
202
+ const fault = kind === "delay" ? {
203
+ ...base,
204
+ kind,
205
+ ms: draw(ranges.delayMs),
206
+ forMs: draw(ranges.delayForMs)
207
+ } : kind === "stall" ? {
208
+ ...base,
209
+ kind,
210
+ ms: draw(ranges.stallMs)
211
+ } : kind === "split" ? {
212
+ ...base,
213
+ kind,
214
+ pieces: draw(ranges.splitPieces),
215
+ gapMs: draw(ranges.splitGapMs)
216
+ } : kind === "reorder" ? {
217
+ ...base,
218
+ kind,
219
+ count: draw(ranges.reorderCount),
220
+ holdMs: draw(ranges.reorderHoldMs)
221
+ } : kind === "duplicate" ? {
222
+ ...base,
223
+ kind,
224
+ copies: draw(ranges.duplicateCopies)
225
+ } : {
226
+ ...base,
227
+ kind
228
+ };
229
+ drawn.push({
230
+ point,
231
+ fault
232
+ });
233
+ }
234
+ drawn.sort((a, b) => a.point - b.point);
235
+ faults.push(...drawn.map((entry) => entry.fault));
236
+ }
237
+ return {
238
+ format: FAULT_PLAN_FORMAT,
239
+ seed,
240
+ faults
241
+ };
242
+ }
243
+ export { DEFAULT_FAULT_RANGES, FAULT_KINDS, FAULT_PLAN_FORMAT, checkFault, checkFaultPlan, checkFaultSpec, faultPlan, faultPlanOf, faultsOf, triggerOf };
244
+
245
+ //# sourceMappingURL=faults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"faults.js","names":[],"sources":["../src/faults.ts"],"sourcesContent":["import { random } from \"@mark1russell7/rigor-generators\";\n\n/**\n * A fault plan tells a proxy which faults to put into which connections, and at which points. A seed fixes the plan\n * before the run, as it fixes a schedule. Thus a record keeps the plan, and a later run applies the same faults.\n *\n * The plan is data, and this module has no I/O. The proxy that applies a plan is the package `@mark1russell7/rigor-proxy`.\n * Each draw of a plan is an integer from `mulberry32`, thus the same seed gives the same plan on each platform.\n */\n\n/** This value is the format of a fault plan. */\nexport const FAULT_PLAN_FORMAT = \"rigor-faults/1\";\n\n/** These are the kinds of faults, in the order that a weight list of a spec uses. */\nexport const FAULT_KINDS = [\"delay\", \"stall\", \"cut\", \"reset\", \"split\", \"reorder\", \"duplicate\"] as const;\n\n/** A kind of fault. */\nexport type FaultKind = (typeof FAULT_KINDS)[number];\n\n/** The direction of a stream. `up` goes from the client to the server, and `down` goes from the server to the client. */\nexport type Direction = \"up\" | \"down\";\n\n/**\n * The point of a fault in the stream of its direction. `ms` counts from the start of the connection. `byte` counts the\n * bytes of the direction before the fault. `frame` counts the frames of the direction before the fault. The framer of\n * the proxy sets what a frame is: a WebSocket frame, a part of an HTTP message or a TCP read.\n */\nexport type Trigger = { readonly ms: number } | { readonly byte: number } | { readonly frame: number };\n\n/** These fields tell where a fault occurs. Connection `i` is the connection that the proxy accepts as number `i`, from 0. */\nexport interface FaultPoint {\n readonly connection: number;\n readonly direction: Direction;\n readonly at: Trigger;\n}\n\n/**\n * One fault of a plan.\n *\n * - `delay`: each frame that the proxy reads in the next `forMs` goes out `ms` later. The proxy does not stop its reads.\n * - `stall`: the proxy stops reading the direction for `ms`. Thus the sender sees back pressure.\n * - `cut`: the proxy ends both directions with a FIN, as a clean close of TCP.\n * - `reset`: the proxy destroys both directions with a TCP reset (RST).\n * - `split`: the frame goes out in `pieces` writes, with `gapMs` between two writes.\n * - `reorder`: the next `count` frames go out in the reverse order. After `holdMs`, the proxy sends the frames that it holds.\n * - `duplicate`: the frame goes out `1 + copies` times.\n */\nexport type Fault = FaultPoint &\n (\n | { readonly kind: \"delay\"; readonly ms: number; readonly forMs: number }\n | { readonly kind: \"stall\"; readonly ms: number }\n | { readonly kind: \"cut\" }\n | { readonly kind: \"reset\" }\n | { readonly kind: \"split\"; readonly pieces: number; readonly gapMs: number }\n | { readonly kind: \"reorder\"; readonly count: number; readonly holdMs: number }\n | { readonly kind: \"duplicate\"; readonly copies: number }\n );\n\n/** A fault plan is data, thus a record can keep the plan that a proxy applied. */\nexport interface FaultPlan {\n readonly format: typeof FAULT_PLAN_FORMAT;\n /** The seed of a plan from a spec, or `null` for a plan that a person wrote. */\n readonly seed: number | string | null;\n /** The faults, in the order of their connections. In a plan from a spec, the faults of a connection are in the order of their points. */\n readonly faults: readonly Fault[];\n}\n\n/** An interval of whole numbers. Both ends are included, and the low end comes first. */\nexport type Interval = readonly [number, number];\n\n/** These intervals hold the parameters of the faults of a spec. */\nexport interface FaultRanges {\n readonly delayMs: Interval;\n readonly delayForMs: Interval;\n readonly stallMs: Interval;\n readonly splitPieces: Interval;\n readonly splitGapMs: Interval;\n readonly reorderCount: Interval;\n readonly reorderHoldMs: Interval;\n readonly duplicateCopies: Interval;\n}\n\n/** These intervals are the default parameters of a spec. */\nexport const DEFAULT_FAULT_RANGES: FaultRanges = {\n delayMs: [50, 2000],\n delayForMs: [100, 5000],\n stallMs: [100, 10_000],\n splitPieces: [2, 8],\n splitGapMs: [0, 50],\n reorderCount: [2, 4],\n reorderHoldMs: [100, 1000],\n duplicateCopies: [1, 2],\n};\n\n/** A spec tells `faultPlan` how many faults to draw, of which kinds, and in which part of each stream. */\nexport interface FaultSpec {\n /** The number of connections that the plan covers. */\n readonly connections: number;\n /** The number of faults of each connection. */\n readonly perConnection: number;\n /** The weight of each kind. A kind without a weight, or with the weight 0, does not occur. */\n readonly kinds: Partial<Readonly<Record<FaultKind, number>>>;\n /** Each point is a whole number from 0 to less than this limit, in the measure of the limit. */\n readonly within: Trigger;\n /** The directions to draw from. The default is both directions. */\n readonly directions?: readonly Direction[];\n /** The intervals of the parameters. Each interval that the spec does not give comes from `DEFAULT_FAULT_RANGES`. */\n readonly ranges?: Partial<FaultRanges>;\n}\n\nconst isWhole = (value: unknown, least: number): boolean => typeof value === \"number\" && Number.isSafeInteger(value) && value >= least;\nconst isTime = (value: unknown, least: number): boolean => typeof value === \"number\" && Number.isFinite(value) && value >= least;\n\n/** This function gives the measure and the value of a point, or `undefined` when the point is not valid. */\nexport function triggerOf(at: Trigger): { readonly measure: \"ms\" | \"byte\" | \"frame\"; readonly value: number } | undefined {\n if (typeof at !== \"object\" || at === null) return undefined;\n const keys = Object.keys(at);\n if (keys.length !== 1) return undefined;\n const measure = keys[0];\n const value = (at as Record<string, unknown>)[measure as string];\n if (measure === \"ms\" && isTime(value, 0)) return { measure, value: value as number };\n if ((measure === \"byte\" || measure === \"frame\") && isWhole(value, 0)) return { measure, value: value as number };\n return undefined;\n}\n\nconst PARAMETERS: Readonly<Record<FaultKind, readonly (readonly [string, \"time\" | \"whole\", number])[]>> = {\n delay: [\n [\"ms\", \"time\", 1],\n [\"forMs\", \"time\", 1],\n ],\n stall: [[\"ms\", \"time\", 1]],\n cut: [],\n reset: [],\n split: [\n [\"pieces\", \"whole\", 2],\n [\"gapMs\", \"time\", 0],\n ],\n reorder: [\n [\"count\", \"whole\", 2],\n [\"holdMs\", \"time\", 1],\n ],\n duplicate: [[\"copies\", \"whole\", 1]],\n};\n\n/** This function gives each problem of one fault. An empty list means that the fault is valid. */\nexport function checkFault(fault: Fault): readonly string[] {\n const out: string[] = [];\n if (!isWhole(fault.connection, 0)) out.push(`The connection is a whole number of 0 or more, not ${String(fault.connection)}.`);\n if (fault.direction !== \"up\" && fault.direction !== \"down\") out.push(`The direction is up or down, not ${String(fault.direction)}.`);\n if (triggerOf(fault.at) === undefined) out.push(`The point has one key of ms, byte or frame, with a value of 0 or more, not ${JSON.stringify(fault.at)}.`);\n if (!(FAULT_KINDS as readonly string[]).includes(fault.kind)) {\n out.push(`The kind is one of ${FAULT_KINDS.join(\", \")}, not ${String(fault.kind)}.`);\n return out;\n }\n for (const [name, type, least] of PARAMETERS[fault.kind]) {\n const value = (fault as unknown as Record<string, unknown>)[name];\n const valid = type === \"whole\" ? isWhole(value, least) : isTime(value, least);\n if (!valid) out.push(`The ${fault.kind} field ${name} is a ${type === \"whole\" ? \"whole number\" : \"time\"} of ${least} or more, not ${String(value)}.`);\n }\n return out;\n}\n\n/** This function gives each problem of a plan. An empty list means that the plan is valid. */\nexport function checkFaultPlan(plan: FaultPlan): readonly string[] {\n const out: string[] = [];\n if (plan.format !== FAULT_PLAN_FORMAT) out.push(`The format is ${FAULT_PLAN_FORMAT}, not ${String(plan.format)}.`);\n if (!Array.isArray(plan.faults)) return [...out, \"The faults are a list.\"];\n plan.faults.forEach((fault, index) => {\n for (const problem of checkFault(fault)) out.push(`The fault ${index}: ${problem}`);\n });\n return out;\n}\n\n/**\n * This function makes a plan from faults that a person wrote. The faults go into the order of their connections, and the\n * faults of one connection keep their order. This function throws a `RangeError` for a fault with problems.\n */\nexport function faultPlanOf(faults: readonly Fault[]): FaultPlan {\n const plan: FaultPlan = { format: FAULT_PLAN_FORMAT, seed: null, faults: [...faults].sort((a, b) => a.connection - b.connection) };\n const problems = checkFaultPlan(plan);\n if (problems.length > 0) throw new RangeError(`faultPlanOf: ${problems.join(\" \")}`);\n return plan;\n}\n\n/** This function gives the faults of one connection, in the order of the plan. */\nexport function faultsOf(plan: FaultPlan, connection: number): readonly Fault[] {\n return plan.faults.filter((fault) => fault.connection === connection);\n}\n\n/** The least value of each interval. A split has 2 pieces or more, and a reorder has 2 frames or more. */\nconst RANGE_LEAST: Readonly<Record<keyof FaultRanges, number>> = {\n delayMs: 1,\n delayForMs: 1,\n stallMs: 1,\n splitPieces: 2,\n splitGapMs: 0,\n reorderCount: 2,\n reorderHoldMs: 1,\n duplicateCopies: 1,\n};\n\n/** This function gives each problem of a spec. An empty list means that the spec is valid. */\nexport function checkFaultSpec(spec: FaultSpec): readonly string[] {\n const out: string[] = [];\n if (!isWhole(spec.connections, 1)) out.push(`The connections are a whole number of 1 or more, not ${String(spec.connections)}.`);\n if (!isWhole(spec.perConnection, 0)) out.push(`The faults of each connection are a whole number of 0 or more, not ${String(spec.perConnection)}.`);\n const weights = Object.entries(spec.kinds ?? {});\n for (const [kind, weight] of weights) {\n if (!(FAULT_KINDS as readonly string[]).includes(kind)) out.push(`The kind ${kind} is not a kind of fault.`);\n else if (!isTime(weight, 0)) out.push(`The weight of ${kind} is a number of 0 or more, not ${String(weight)}.`);\n }\n const known = weights.filter(([kind]) => (FAULT_KINDS as readonly string[]).includes(kind));\n if (!known.some(([, weight]) => typeof weight === \"number\" && weight > 0)) out.push(\"At least one kind of fault has a weight more than 0.\");\n const within = triggerOf(spec.within);\n if (within === undefined || !Number.isSafeInteger(within.value) || within.value < 1) {\n out.push(`The limit of the points has one key of ms, byte or frame, with a whole value of 1 or more, not ${JSON.stringify(spec.within)}.`);\n }\n const directions = spec.directions ?? [\"up\", \"down\"];\n if (directions.length === 0 || directions.some((d) => d !== \"up\" && d !== \"down\")) out.push(`The directions are up or down, not ${JSON.stringify(spec.directions)}.`);\n for (const [name, interval] of Object.entries(spec.ranges ?? {})) {\n if (!Object.hasOwn(RANGE_LEAST, name)) {\n out.push(`The range ${name} is not a range of a fault parameter.`);\n continue;\n }\n const [low, high] = Array.isArray(interval) ? interval : [];\n const least = RANGE_LEAST[name as keyof FaultRanges];\n if (!isWhole(low, least) || !isWhole(high, least) || (high as number) < (low as number)) {\n out.push(`The range ${name} has two whole numbers of ${least} or more, the low one first, not ${JSON.stringify(interval)}.`);\n }\n }\n return out;\n}\n\n/**\n * This function draws a fault plan from a spec and a seed. Each connection draws from its own stream, `fork` of the seed.\n * Thus a spec with more connections keeps the faults of the first connections. The faults of a connection are in the\n * order of their points. This function throws a `RangeError` for a spec with problems.\n */\nexport function faultPlan(spec: FaultSpec, seed: number | string): FaultPlan {\n const problems = checkFaultSpec(spec);\n if (problems.length > 0) throw new RangeError(`faultPlan: ${problems.join(\" \")}`);\n const ranges: FaultRanges = { ...DEFAULT_FAULT_RANGES, ...spec.ranges };\n const kinds = FAULT_KINDS.filter((kind) => (spec.kinds[kind] ?? 0) > 0);\n const weights = kinds.map((kind) => spec.kinds[kind] as number);\n const directions = spec.directions ?? ([\"up\", \"down\"] as const);\n const within = triggerOf(spec.within) as { readonly measure: \"ms\" | \"byte\" | \"frame\"; readonly value: number };\n const root = random(seed);\n const faults: Fault[] = [];\n for (let connection = 0; connection < spec.connections; connection += 1) {\n const rng = root.fork(`connection ${connection}`);\n const draw = (interval: Interval): number => rng.int(interval[0], interval[1]);\n const drawn: { readonly point: number; readonly fault: Fault }[] = [];\n for (let index = 0; index < spec.perConnection; index += 1) {\n const kind = kinds[rng.weighted(weights)] as FaultKind;\n const direction = rng.pick(directions);\n const point = rng.int(0, within.value - 1);\n const base = { connection, direction, at: { [within.measure]: point } as Trigger };\n const fault: Fault =\n kind === \"delay\"\n ? { ...base, kind, ms: draw(ranges.delayMs), forMs: draw(ranges.delayForMs) }\n : kind === \"stall\"\n ? { ...base, kind, ms: draw(ranges.stallMs) }\n : kind === \"split\"\n ? { ...base, kind, pieces: draw(ranges.splitPieces), gapMs: draw(ranges.splitGapMs) }\n : kind === \"reorder\"\n ? { ...base, kind, count: draw(ranges.reorderCount), holdMs: draw(ranges.reorderHoldMs) }\n : kind === \"duplicate\"\n ? { ...base, kind, copies: draw(ranges.duplicateCopies) }\n : { ...base, kind };\n drawn.push({ point, fault });\n }\n drawn.sort((a, b) => a.point - b.point);\n faults.push(...drawn.map((entry) => entry.fault));\n }\n return { format: FAULT_PLAN_FORMAT, seed, faults };\n}\n"],"mappings":";;;;;;;;;;AAWA,MAAa,oBAAoB;;AAGjC,MAAa,cAAc;CAAC;CAAS;CAAS;CAAO;CAAS;CAAS;CAAW;AAAW;;AAqE7F,MAAa,uBAAoC;CAC/C,SAAS,CAAC,IAAI,GAAI;CAClB,YAAY,CAAC,KAAK,GAAI;CACtB,SAAS,CAAC,KAAK,GAAM;CACrB,aAAa,CAAC,GAAG,CAAC;CAClB,YAAY,CAAC,GAAG,EAAE;CAClB,cAAc,CAAC,GAAG,CAAC;CACnB,eAAe,CAAC,KAAK,GAAI;CACzB,iBAAiB,CAAC,GAAG,CAAC;AACxB;AAkBA,MAAM,WAAW,OAAgB,UAA2B,OAAO,UAAU,YAAY,OAAO,cAAc,KAAK,KAAK,SAAS;AACjI,MAAM,UAAU,OAAgB,UAA2B,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,KAAK,SAAS;;AAG3H,SAAgB,UAAU,IAAgG;CACxH,IAAI,OAAO,OAAO,YAAY,OAAO,MAAM,OAAO,KAAA;CAClD,MAAM,OAAO,OAAO,KAAK,EAAE;CAC3B,IAAI,KAAK,WAAW,GAAG,OAAO,KAAA;CAC9B,MAAM,UAAU,KAAK;CACrB,MAAM,QAAS,GAA+B;CAC9C,IAAI,YAAY,QAAQ,OAAO,OAAO,CAAC,GAAG,OAAO;EAAE;EAAgB;CAAgB;CACnF,KAAK,YAAY,UAAU,YAAY,YAAY,QAAQ,OAAO,CAAC,GAAG,OAAO;EAAE;EAAgB;CAAgB;AAEjH;AAEA,MAAM,aAAoG;CACxG,OAAO,CACL;EAAC;EAAM;EAAQ;CAAC,GAChB;EAAC;EAAS;EAAQ;CAAC,CACrB;CACA,OAAO,CAAC;EAAC;EAAM;EAAQ;CAAC,CAAC;CACzB,KAAK,CAAC;CACN,OAAO,CAAC;CACR,OAAO,CACL;EAAC;EAAU;EAAS;CAAC,GACrB;EAAC;EAAS;EAAQ;CAAC,CACrB;CACA,SAAS,CACP;EAAC;EAAS;EAAS;CAAC,GACpB;EAAC;EAAU;EAAQ;CAAC,CACtB;CACA,WAAW,CAAC;EAAC;EAAU;EAAS;CAAC,CAAC;AACpC;;AAGA,SAAgB,WAAW,OAAiC;CAC1D,MAAM,MAAgB,CAAC;CACvB,IAAI,CAAC,QAAQ,MAAM,YAAY,CAAC,GAAG,IAAI,KAAK,sDAAsD,OAAO,MAAM,UAAU,EAAE,EAAE;CAC7H,IAAI,MAAM,cAAc,QAAQ,MAAM,cAAc,QAAQ,IAAI,KAAK,oCAAoC,OAAO,MAAM,SAAS,EAAE,EAAE;CACnI,IAAI,UAAU,MAAM,EAAE,MAAM,KAAA,GAAW,IAAI,KAAK,8EAA8E,KAAK,UAAU,MAAM,EAAE,EAAE,EAAE;CACzJ,IAAI,CAAE,YAAkC,SAAS,MAAM,IAAI,GAAG;EAC5D,IAAI,KAAK,sBAAsB,YAAY,KAAK,IAAI,EAAE,QAAQ,OAAO,MAAM,IAAI,EAAE,EAAE;EACnF,OAAO;CACT;CACA,KAAK,MAAM,CAAC,MAAM,MAAM,UAAU,WAAW,MAAM,OAAO;EACxD,MAAM,QAAS,MAA6C;EAE5D,IAAI,EADU,SAAS,UAAU,QAAQ,OAAO,KAAK,IAAI,OAAO,OAAO,KAAK,IAChE,IAAI,KAAK,OAAO,MAAM,KAAK,SAAS,KAAK,QAAQ,SAAS,UAAU,iBAAiB,OAAO,MAAM,MAAM,gBAAgB,OAAO,KAAK,EAAE,EAAE;CACtJ;CACA,OAAO;AACT;;AAGA,SAAgB,eAAe,MAAoC;CACjE,MAAM,MAAgB,CAAC;CACvB,IAAI,KAAK,WAAA,kBAA8B,IAAI,KAAK,iBAAiB,kBAAkB,QAAQ,OAAO,KAAK,MAAM,EAAE,EAAE;CACjH,IAAI,CAAC,MAAM,QAAQ,KAAK,MAAM,GAAG,OAAO,CAAC,GAAG,KAAK,wBAAwB;CACzE,KAAK,OAAO,SAAS,OAAO,UAAU;EACpC,KAAK,MAAM,WAAW,WAAW,KAAK,GAAG,IAAI,KAAK,aAAa,MAAM,IAAI,SAAS;CACpF,CAAC;CACD,OAAO;AACT;;;;;AAMA,SAAgB,YAAY,QAAqC;CAC/D,MAAM,OAAkB;EAAE,QAAQ;EAAmB,MAAM;EAAM,QAAQ,CAAC,GAAG,MAAM,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,aAAa,EAAE,UAAU;CAAE;CACjI,MAAM,WAAW,eAAe,IAAI;CACpC,IAAI,SAAS,SAAS,GAAG,MAAM,IAAI,WAAW,gBAAgB,SAAS,KAAK,GAAG,GAAG;CAClF,OAAO;AACT;;AAGA,SAAgB,SAAS,MAAiB,YAAsC;CAC9E,OAAO,KAAK,OAAO,QAAQ,UAAU,MAAM,eAAe,UAAU;AACtE;;AAGA,MAAM,cAA2D;CAC/D,SAAS;CACT,YAAY;CACZ,SAAS;CACT,aAAa;CACb,YAAY;CACZ,cAAc;CACd,eAAe;CACf,iBAAiB;AACnB;;AAGA,SAAgB,eAAe,MAAoC;CACjE,MAAM,MAAgB,CAAC;CACvB,IAAI,CAAC,QAAQ,KAAK,aAAa,CAAC,GAAG,IAAI,KAAK,wDAAwD,OAAO,KAAK,WAAW,EAAE,EAAE;CAC/H,IAAI,CAAC,QAAQ,KAAK,eAAe,CAAC,GAAG,IAAI,KAAK,sEAAsE,OAAO,KAAK,aAAa,EAAE,EAAE;CACjJ,MAAM,UAAU,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC;CAC/C,KAAK,MAAM,CAAC,MAAM,WAAW,SAC3B,IAAI,CAAE,YAAkC,SAAS,IAAI,GAAG,IAAI,KAAK,YAAY,KAAK,yBAAyB;MACtG,IAAI,CAAC,OAAO,QAAQ,CAAC,GAAG,IAAI,KAAK,iBAAiB,KAAK,iCAAiC,OAAO,MAAM,EAAE,EAAE;CAGhH,IAAI,CADU,QAAQ,QAAQ,CAAC,UAAW,YAAkC,SAAS,IAAI,CAChF,CAAC,CAAC,MAAM,GAAG,YAAY,OAAO,WAAW,YAAY,SAAS,CAAC,GAAG,IAAI,KAAK,sDAAsD;CAC1I,MAAM,SAAS,UAAU,KAAK,MAAM;CACpC,IAAI,WAAW,KAAA,KAAa,CAAC,OAAO,cAAc,OAAO,KAAK,KAAK,OAAO,QAAQ,GAChF,IAAI,KAAK,kGAAkG,KAAK,UAAU,KAAK,MAAM,EAAE,EAAE;CAE3I,MAAM,aAAa,KAAK,cAAc,CAAC,MAAM,MAAM;CACnD,IAAI,WAAW,WAAW,KAAK,WAAW,MAAM,MAAM,MAAM,QAAQ,MAAM,MAAM,GAAG,IAAI,KAAK,sCAAsC,KAAK,UAAU,KAAK,UAAU,EAAE,EAAE;CACpK,KAAK,MAAM,CAAC,MAAM,aAAa,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC,GAAG;EAChE,IAAI,CAAC,OAAO,OAAO,aAAa,IAAI,GAAG;GACrC,IAAI,KAAK,aAAa,KAAK,sCAAsC;GACjE;EACF;EACA,MAAM,CAAC,KAAK,QAAQ,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC;EAC1D,MAAM,QAAQ,YAAY;EAC1B,IAAI,CAAC,QAAQ,KAAK,KAAK,KAAK,CAAC,QAAQ,MAAM,KAAK,KAAM,OAAmB,KACvE,IAAI,KAAK,aAAa,KAAK,4BAA4B,MAAM,mCAAmC,KAAK,UAAU,QAAQ,EAAE,EAAE;CAE/H;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,UAAU,MAAiB,MAAkC;CAC3E,MAAM,WAAW,eAAe,IAAI;CACpC,IAAI,SAAS,SAAS,GAAG,MAAM,IAAI,WAAW,cAAc,SAAS,KAAK,GAAG,GAAG;CAChF,MAAM,SAAsB;EAAE,GAAG;EAAsB,GAAG,KAAK;CAAO;CACtE,MAAM,QAAQ,YAAY,QAAQ,UAAU,KAAK,MAAM,SAAS,KAAK,CAAC;CACtE,MAAM,UAAU,MAAM,KAAK,SAAS,KAAK,MAAM,KAAe;CAC9D,MAAM,aAAa,KAAK,cAAe,CAAC,MAAM,MAAM;CACpD,MAAM,SAAS,UAAU,KAAK,MAAM;CACpC,MAAM,OAAO,OAAO,IAAI;CACxB,MAAM,SAAkB,CAAC;CACzB,KAAK,IAAI,aAAa,GAAG,aAAa,KAAK,aAAa,cAAc,GAAG;EACvE,MAAM,MAAM,KAAK,KAAK,cAAc,YAAY;EAChD,MAAM,QAAQ,aAA+B,IAAI,IAAI,SAAS,IAAI,SAAS,EAAE;EAC7E,MAAM,QAA6D,CAAC;EACpE,KAAK,IAAI,QAAQ,GAAG,QAAQ,KAAK,eAAe,SAAS,GAAG;GAC1D,MAAM,OAAO,MAAM,IAAI,SAAS,OAAO;GACvC,MAAM,YAAY,IAAI,KAAK,UAAU;GACrC,MAAM,QAAQ,IAAI,IAAI,GAAG,OAAO,QAAQ,CAAC;GACzC,MAAM,OAAO;IAAE;IAAY;IAAW,IAAI,GAAG,OAAO,UAAU,MAAM;GAAa;GACjF,MAAM,QACJ,SAAS,UACL;IAAE,GAAG;IAAM;IAAM,IAAI,KAAK,OAAO,OAAO;IAAG,OAAO,KAAK,OAAO,UAAU;GAAE,IAC1E,SAAS,UACP;IAAE,GAAG;IAAM;IAAM,IAAI,KAAK,OAAO,OAAO;GAAE,IAC1C,SAAS,UACP;IAAE,GAAG;IAAM;IAAM,QAAQ,KAAK,OAAO,WAAW;IAAG,OAAO,KAAK,OAAO,UAAU;GAAE,IAClF,SAAS,YACP;IAAE,GAAG;IAAM;IAAM,OAAO,KAAK,OAAO,YAAY;IAAG,QAAQ,KAAK,OAAO,aAAa;GAAE,IACtF,SAAS,cACP;IAAE,GAAG;IAAM;IAAM,QAAQ,KAAK,OAAO,eAAe;GAAE,IACtD;IAAE,GAAG;IAAM;GAAK;GAC9B,MAAM,KAAK;IAAE;IAAO;GAAM,CAAC;EAC7B;EACA,MAAM,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;EACtC,OAAO,KAAK,GAAG,MAAM,KAAK,UAAU,MAAM,KAAK,CAAC;CAClD;CACA,OAAO;EAAE,QAAQ;EAAmB;EAAM;CAAO;AACnD"}
package/build/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ClosedLoopOptions, ClosedLoopRun, LoopRequest, closedLoopPlan } from "./closed.js";
2
+ import { DEFAULT_FAULT_RANGES, Direction, FAULT_KINDS, FAULT_PLAN_FORMAT, Fault, FaultKind, FaultPlan, FaultPoint, FaultRanges, FaultSpec, Interval, Trigger, checkFault, checkFaultPlan, checkFaultSpec, faultPlan, faultPlanOf, faultsOf, triggerOf } from "./faults.js";
2
3
  import { Completion, LoadMeasure, ScheduleCheck, SecondSample, latencyQuantile, measureLoad, scheduleHeld } from "./measure.js";
3
4
  import { Arrivals, Phase, Profile, SCHEDULE_FORMAT, Schedule, ScheduleOptions, checkProfile, constant, expectedArrivals, ramp, schedule, spike, steps } from "./schedule.js";
4
- export { type Arrivals, type ClosedLoopOptions, type ClosedLoopRun, type Completion, type LoadMeasure, type LoopRequest, type Phase, type Profile, SCHEDULE_FORMAT, type Schedule, type ScheduleCheck, type ScheduleOptions, type SecondSample, checkProfile, closedLoopPlan, constant, expectedArrivals, latencyQuantile, measureLoad, ramp, schedule, scheduleHeld, spike, steps };
5
+ export { type Arrivals, type ClosedLoopOptions, type ClosedLoopRun, type Completion, DEFAULT_FAULT_RANGES, type Direction, FAULT_KINDS, FAULT_PLAN_FORMAT, type Fault, type FaultKind, type FaultPlan, type FaultPoint, type FaultRanges, type FaultSpec, type Interval, type LoadMeasure, type LoopRequest, type Phase, type Profile, SCHEDULE_FORMAT, type Schedule, type ScheduleCheck, type ScheduleOptions, type SecondSample, type Trigger, checkFault, checkFaultPlan, checkFaultSpec, checkProfile, closedLoopPlan, constant, expectedArrivals, faultPlan, faultPlanOf, faultsOf, latencyQuantile, measureLoad, ramp, schedule, scheduleHeld, spike, steps, triggerOf };
package/build/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { closedLoopPlan } from "./closed.js";
2
+ import { DEFAULT_FAULT_RANGES, FAULT_KINDS, FAULT_PLAN_FORMAT, checkFault, checkFaultPlan, checkFaultSpec, faultPlan, faultPlanOf, faultsOf, triggerOf } from "./faults.js";
2
3
  import { latencyQuantile, measureLoad, scheduleHeld } from "./measure.js";
3
4
  import { SCHEDULE_FORMAT, checkProfile, constant, expectedArrivals, ramp, schedule, spike, steps } from "./schedule.js";
4
- export { SCHEDULE_FORMAT, checkProfile, closedLoopPlan, constant, expectedArrivals, latencyQuantile, measureLoad, ramp, schedule, scheduleHeld, spike, steps };
5
+ export { DEFAULT_FAULT_RANGES, FAULT_KINDS, FAULT_PLAN_FORMAT, SCHEDULE_FORMAT, checkFault, checkFaultPlan, checkFaultSpec, checkProfile, closedLoopPlan, constant, expectedArrivals, faultPlan, faultPlanOf, faultsOf, latencyQuantile, measureLoad, ramp, schedule, scheduleHeld, spike, steps, triggerOf };
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@mark1russell7/rigor-chaos",
4
- "version": "0.1.4",
5
- "description": "Open-loop load schedules with planned arrival times from a rate profile, and a measure of latency from the planned time with no coordinated omission.",
4
+ "version": "0.1.6",
5
+ "description": "Open-loop load schedules, seeded fault plans for a proxy, and a measure of latency from the planned time with no coordinated omission.",
6
6
  "keywords": [
7
7
  "load",
8
8
  "open-loop",
9
9
  "schedule",
10
10
  "poisson",
11
11
  "chaos",
12
+ "fault-injection",
13
+ "fault-plan",
12
14
  "coordinated-omission",
13
15
  "latency",
14
16
  "testing",
@@ -38,8 +40,8 @@
38
40
  "node": ">=22"
39
41
  },
40
42
  "dependencies": {
41
- "@mark1russell7/rigor-generators": "^0.1.14",
42
- "@mark1russell7/rigor-statistics": "^0.1.27"
43
+ "@mark1russell7/rigor-generators": "^0.1.16",
44
+ "@mark1russell7/rigor-statistics": "^0.1.29"
43
45
  },
44
46
  "devDependencies": {
45
47
  "@mark1russell7/cue": "^2.0.0",
@@ -59,7 +61,8 @@
59
61
  "exports": {
60
62
  ".": {
61
63
  "types": "./build/index.d.ts",
62
- "import": "./build/index.js"
64
+ "import": "./build/index.js",
65
+ "default": "./build/index.js"
63
66
  },
64
67
  "./package.json": "./package.json"
65
68
  }