@aplaytest/flaky 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -0
- package/dist/analyze.d.ts +46 -0
- package/dist/analyze.d.ts.map +1 -0
- package/dist/analyze.js +68 -0
- package/dist/analyze.js.map +1 -0
- package/dist/bisect.d.ts +69 -0
- package/dist/bisect.d.ts.map +1 -0
- package/dist/bisect.js +177 -0
- package/dist/bisect.js.map +1 -0
- package/dist/classify.d.ts +57 -0
- package/dist/classify.d.ts.map +1 -0
- package/dist/classify.js +254 -0
- package/dist/classify.js.map +1 -0
- package/dist/codemod.d.ts +45 -0
- package/dist/codemod.d.ts.map +1 -0
- package/dist/codemod.js +322 -0
- package/dist/codemod.js.map +1 -0
- package/dist/features.d.ts +55 -0
- package/dist/features.d.ts.map +1 -0
- package/dist/features.js +203 -0
- package/dist/features.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/quarantine.d.ts +76 -0
- package/dist/quarantine.d.ts.map +1 -0
- package/dist/quarantine.js +101 -0
- package/dist/quarantine.js.map +1 -0
- package/dist/score.d.ts +85 -0
- package/dist/score.d.ts.map +1 -0
- package/dist/score.js +140 -0
- package/dist/score.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root-cause classification for flaky tests.
|
|
3
|
+
*
|
|
4
|
+
* Rules over measured features, evaluated by priority, first match wins. Each
|
|
5
|
+
* verdict carries the evidence that produced it, because a prescription a
|
|
6
|
+
* human cannot check is a prescription a human will not follow.
|
|
7
|
+
*
|
|
8
|
+
* The highest-priority rule is `genuine-regression`, and it is the one that
|
|
9
|
+
* matters most: a real regression misfiled as flake gets retried, quarantined,
|
|
10
|
+
* and shipped. The engine must be able to say "this is not flaky" and mean it.
|
|
11
|
+
*/
|
|
12
|
+
import type { FlakeFeatures } from './features.js';
|
|
13
|
+
import type { FlakeScore } from './score.js';
|
|
14
|
+
export declare const FLAKE_CLASSES: readonly ["genuine-regression", "consistently-failing", "test-pollution", "resource-contention", "network", "animation", "timing", "data-dependency", "environment", "unclassified"];
|
|
15
|
+
export type FlakeClass = (typeof FLAKE_CLASSES)[number];
|
|
16
|
+
/** What to do about it. Deliberately not "retry" for most classes. */
|
|
17
|
+
export type Prescription = 'investigate-regression' | 'fix-or-delete' | 'isolate' | 'reduce-parallelism-or-harden' | 'harden-wait-or-fix-app' | 'wait-for-stable' | 'web-first-assertion' | 'narrow-the-view' | 'browser-specific-investigation' | 'needs-more-data';
|
|
18
|
+
export interface Classification {
|
|
19
|
+
readonly class: FlakeClass;
|
|
20
|
+
readonly prescription: Prescription;
|
|
21
|
+
readonly confidence: 'low' | 'medium' | 'high';
|
|
22
|
+
/** Measured statements that justify the verdict. */
|
|
23
|
+
readonly evidence: readonly string[];
|
|
24
|
+
/** Whether retrying this class masks the problem instead of surviving it. */
|
|
25
|
+
readonly retryable: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Classify a scored test into a flake class.
|
|
29
|
+
*
|
|
30
|
+
* Priority-ordered rules; `genuine-regression` and `consistently-failing`
|
|
31
|
+
* outrank every flake class, because misfiling a broken test as flaky gets
|
|
32
|
+
* it retried and quarantined instead of fixed.
|
|
33
|
+
*
|
|
34
|
+
* @param features - Measurements from `extractFeatures`.
|
|
35
|
+
* @param score - Result of `scoreTest`.
|
|
36
|
+
* @returns A class, a prescription, the evidence strings that fired, and
|
|
37
|
+
* whether retries would help.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const score = scoreTest(attempts);
|
|
42
|
+
* const features = extractFeatures(attempts, allAttempts);
|
|
43
|
+
* const verdict = classifyFlake(features, score);
|
|
44
|
+
* // verdict.class === 'resource-contention'
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function classifyFlake(features: FlakeFeatures, score: FlakeScore): Classification;
|
|
48
|
+
/**
|
|
49
|
+
* Retry only what retrying can actually survive.
|
|
50
|
+
*
|
|
51
|
+
* Blanket retries hide real bugs, which is the failure mode a flaky-test tool
|
|
52
|
+
* is most likely to cause. A regression, a polluted test, or a data-dependent
|
|
53
|
+
* assertion fails just as reliably the second time — retrying them only
|
|
54
|
+
* converts a red build into a slow red build, or worse, a false green.
|
|
55
|
+
*/
|
|
56
|
+
export declare function shouldRetry(classification: Classification): boolean;
|
|
57
|
+
//# sourceMappingURL=classify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../src/classify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAO,MAAM,aAAa,sLAWhB,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAExD,sEAAsE;AACtE,MAAM,MAAM,YAAY,GACpB,wBAAwB,GACxB,eAAe,GACf,SAAS,GACT,8BAA8B,GAC9B,wBAAwB,GACxB,iBAAiB,GACjB,qBAAqB,GACrB,iBAAiB,GACjB,gCAAgC,GAChC,iBAAiB,CAAC;AAEtB,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC/C,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAmKD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,UAAU,GAAG,cAAc,CAkCxF;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,cAAc,EAAE,cAAc,GAAG,OAAO,CAEnE"}
|
package/dist/classify.js
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root-cause classification for flaky tests.
|
|
3
|
+
*
|
|
4
|
+
* Rules over measured features, evaluated by priority, first match wins. Each
|
|
5
|
+
* verdict carries the evidence that produced it, because a prescription a
|
|
6
|
+
* human cannot check is a prescription a human will not follow.
|
|
7
|
+
*
|
|
8
|
+
* The highest-priority rule is `genuine-regression`, and it is the one that
|
|
9
|
+
* matters most: a real regression misfiled as flake gets retried, quarantined,
|
|
10
|
+
* and shipped. The engine must be able to say "this is not flaky" and mean it.
|
|
11
|
+
*/
|
|
12
|
+
export const FLAKE_CLASSES = [
|
|
13
|
+
'genuine-regression',
|
|
14
|
+
'consistently-failing',
|
|
15
|
+
'test-pollution',
|
|
16
|
+
'resource-contention',
|
|
17
|
+
'network',
|
|
18
|
+
'animation',
|
|
19
|
+
'timing',
|
|
20
|
+
'data-dependency',
|
|
21
|
+
'environment',
|
|
22
|
+
'unclassified',
|
|
23
|
+
];
|
|
24
|
+
const percent = (value) => `${(value * 100).toFixed(0)}%`;
|
|
25
|
+
function kindShare(features, kinds) {
|
|
26
|
+
if (features.totalFailures === 0)
|
|
27
|
+
return 0;
|
|
28
|
+
const matched = kinds.reduce((sum, kind) => sum + (features.failureKinds[kind] ?? 0), 0);
|
|
29
|
+
return matched / features.totalFailures;
|
|
30
|
+
}
|
|
31
|
+
const RULES = [
|
|
32
|
+
{
|
|
33
|
+
class: 'genuine-regression',
|
|
34
|
+
priority: 100,
|
|
35
|
+
prescription: 'investigate-regression',
|
|
36
|
+
confidence: 'high',
|
|
37
|
+
retryable: false,
|
|
38
|
+
evaluate: (features, score) => {
|
|
39
|
+
if (!features.commitBoundary)
|
|
40
|
+
return null;
|
|
41
|
+
if (score.transitionDensity >= 0.2)
|
|
42
|
+
return null;
|
|
43
|
+
if (features.retryFlips > 0)
|
|
44
|
+
return null;
|
|
45
|
+
return [
|
|
46
|
+
`failures begin at a commit boundary and persist (${features.commitsObserved} commits observed)`,
|
|
47
|
+
`outcome flips are rare (density ${score.transitionDensity.toFixed(2)}) — this is a state change, not noise`,
|
|
48
|
+
`no failed-then-passed retry within a single run`,
|
|
49
|
+
];
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
// Checked ahead of every flake class. A test that always fails is not
|
|
54
|
+
// flaky in any sense, and handing it a flake prescription ("add a
|
|
55
|
+
// web-first assertion", "retry helps: yes") sends someone tuning waits on
|
|
56
|
+
// a test that is simply broken.
|
|
57
|
+
class: 'consistently-failing',
|
|
58
|
+
priority: 95,
|
|
59
|
+
prescription: 'fix-or-delete',
|
|
60
|
+
confidence: 'high',
|
|
61
|
+
retryable: false,
|
|
62
|
+
evaluate: features => {
|
|
63
|
+
if (features.totalAttempts === 0)
|
|
64
|
+
return null;
|
|
65
|
+
if (features.totalFailures !== features.totalAttempts)
|
|
66
|
+
return null;
|
|
67
|
+
return [
|
|
68
|
+
`every one of ${features.totalAttempts} attempts failed — this is deterministic, not flaky`,
|
|
69
|
+
'fix it or delete it; quarantine is not the answer for a test that never passes',
|
|
70
|
+
];
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
class: 'test-pollution',
|
|
75
|
+
priority: 90,
|
|
76
|
+
prescription: 'isolate',
|
|
77
|
+
confidence: 'high',
|
|
78
|
+
retryable: false,
|
|
79
|
+
evaluate: features => {
|
|
80
|
+
if (features.coScheduleLift <= 2.5 || features.coScheduleSuspects.length === 0)
|
|
81
|
+
return null;
|
|
82
|
+
return [
|
|
83
|
+
`failure is ${features.coScheduleLift.toFixed(1)}× more likely when co-scheduled with another test`,
|
|
84
|
+
`suspects: ${features.coScheduleSuspects.join(', ')}`,
|
|
85
|
+
];
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
class: 'resource-contention',
|
|
90
|
+
priority: 80,
|
|
91
|
+
prescription: 'reduce-parallelism-or-harden',
|
|
92
|
+
confidence: 'high',
|
|
93
|
+
retryable: true,
|
|
94
|
+
evaluate: features => {
|
|
95
|
+
// Gate on the rate DIFFERENCE, not the correlation: with a rare outcome
|
|
96
|
+
// the correlation ceiling collapses, and a test that never fails at one
|
|
97
|
+
// worker but fails a third of the time at eight scores only ~0.41.
|
|
98
|
+
if (!features.workerCountVaried)
|
|
99
|
+
return null;
|
|
100
|
+
if (features.workerLoadDelta <= 0.15)
|
|
101
|
+
return null;
|
|
102
|
+
if (features.projectConcentration < 0.8)
|
|
103
|
+
return null;
|
|
104
|
+
return [
|
|
105
|
+
`failures are ${percent(features.workerLoadDelta)} more likely above the median worker count`,
|
|
106
|
+
`${percent(features.projectConcentration)} of failures land in a single project`,
|
|
107
|
+
`worker/failure correlation r = ${features.workerCorrelation.toFixed(2)}`,
|
|
108
|
+
];
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
class: 'network',
|
|
113
|
+
priority: 70,
|
|
114
|
+
prescription: 'harden-wait-or-fix-app',
|
|
115
|
+
confidence: 'medium',
|
|
116
|
+
retryable: true,
|
|
117
|
+
evaluate: features => {
|
|
118
|
+
const share = kindShare(features, ['network_error', 'navigation_failure']);
|
|
119
|
+
if (share < 0.3)
|
|
120
|
+
return null;
|
|
121
|
+
return [`${percent(share)} of failures are network or navigation errors`];
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
class: 'animation',
|
|
126
|
+
priority: 60,
|
|
127
|
+
prescription: 'wait-for-stable',
|
|
128
|
+
confidence: 'medium',
|
|
129
|
+
retryable: true,
|
|
130
|
+
evaluate: features => {
|
|
131
|
+
const share = kindShare(features, ['locator_not_actionable']);
|
|
132
|
+
if (share < 0.5)
|
|
133
|
+
return null;
|
|
134
|
+
return [
|
|
135
|
+
`${percent(share)} of failures are "element found but not actionable" — intercepted, unstable, or mid-transition`,
|
|
136
|
+
];
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
class: 'timing',
|
|
141
|
+
priority: 50,
|
|
142
|
+
prescription: 'web-first-assertion',
|
|
143
|
+
confidence: 'medium',
|
|
144
|
+
retryable: true,
|
|
145
|
+
evaluate: features => {
|
|
146
|
+
const share = kindShare(features, ['assertion_visibility', 'locator_not_found']);
|
|
147
|
+
if (share < 0.5)
|
|
148
|
+
return null;
|
|
149
|
+
if (features.durationRatio < 1.5)
|
|
150
|
+
return null;
|
|
151
|
+
return [
|
|
152
|
+
`${percent(share)} of failures are waiting for something that never appeared`,
|
|
153
|
+
`failing runs take ${features.durationRatio.toFixed(1)}× as long as passing ones — the test waits, then gives up`,
|
|
154
|
+
];
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
class: 'data-dependency',
|
|
159
|
+
priority: 40,
|
|
160
|
+
prescription: 'narrow-the-view',
|
|
161
|
+
confidence: 'medium',
|
|
162
|
+
retryable: false,
|
|
163
|
+
evaluate: features => {
|
|
164
|
+
const share = kindShare(features, ['assertion_value_mismatch']);
|
|
165
|
+
if (share < 0.5)
|
|
166
|
+
return null;
|
|
167
|
+
return [
|
|
168
|
+
`${percent(share)} of failures are value mismatches — the data moved, not the timing`,
|
|
169
|
+
`retrying will not help; narrow the view (search or filter) before asserting`,
|
|
170
|
+
];
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
class: 'environment',
|
|
175
|
+
priority: 30,
|
|
176
|
+
prescription: 'browser-specific-investigation',
|
|
177
|
+
confidence: 'medium',
|
|
178
|
+
retryable: true,
|
|
179
|
+
evaluate: features => {
|
|
180
|
+
if (features.projectConcentration < 0.95)
|
|
181
|
+
return null;
|
|
182
|
+
if (features.totalFailures < 3)
|
|
183
|
+
return null;
|
|
184
|
+
return [
|
|
185
|
+
`every failure is confined to one project (${percent(features.projectConcentration)}) — browser or device specific`,
|
|
186
|
+
];
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
/**
|
|
191
|
+
* Classify a scored test into a flake class.
|
|
192
|
+
*
|
|
193
|
+
* Priority-ordered rules; `genuine-regression` and `consistently-failing`
|
|
194
|
+
* outrank every flake class, because misfiling a broken test as flaky gets
|
|
195
|
+
* it retried and quarantined instead of fixed.
|
|
196
|
+
*
|
|
197
|
+
* @param features - Measurements from `extractFeatures`.
|
|
198
|
+
* @param score - Result of `scoreTest`.
|
|
199
|
+
* @returns A class, a prescription, the evidence strings that fired, and
|
|
200
|
+
* whether retries would help.
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```ts
|
|
204
|
+
* const score = scoreTest(attempts);
|
|
205
|
+
* const features = extractFeatures(attempts, allAttempts);
|
|
206
|
+
* const verdict = classifyFlake(features, score);
|
|
207
|
+
* // verdict.class === 'resource-contention'
|
|
208
|
+
* ```
|
|
209
|
+
*/
|
|
210
|
+
export function classifyFlake(features, score) {
|
|
211
|
+
if (score.insufficientData) {
|
|
212
|
+
return {
|
|
213
|
+
class: 'unclassified',
|
|
214
|
+
prescription: 'needs-more-data',
|
|
215
|
+
confidence: 'low',
|
|
216
|
+
evidence: [`only ${score.rawN} conclusive attempts recorded — not enough to judge`],
|
|
217
|
+
retryable: false,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
for (const rule of [...RULES].sort((a, b) => b.priority - a.priority)) {
|
|
221
|
+
const evidence = rule.evaluate(features, score);
|
|
222
|
+
if (evidence !== null && evidence.length > 0) {
|
|
223
|
+
return {
|
|
224
|
+
class: rule.class,
|
|
225
|
+
prescription: rule.prescription,
|
|
226
|
+
confidence: rule.confidence,
|
|
227
|
+
evidence,
|
|
228
|
+
retryable: rule.retryable,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
class: 'unclassified',
|
|
234
|
+
prescription: 'needs-more-data',
|
|
235
|
+
confidence: 'low',
|
|
236
|
+
evidence: [
|
|
237
|
+
`flake score ${score.score.toFixed(2)} over ${score.rawN} attempts, but no rule matched`,
|
|
238
|
+
'bisect to gather a decisive signal',
|
|
239
|
+
],
|
|
240
|
+
retryable: false,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Retry only what retrying can actually survive.
|
|
245
|
+
*
|
|
246
|
+
* Blanket retries hide real bugs, which is the failure mode a flaky-test tool
|
|
247
|
+
* is most likely to cause. A regression, a polluted test, or a data-dependent
|
|
248
|
+
* assertion fails just as reliably the second time — retrying them only
|
|
249
|
+
* converts a red build into a slow red build, or worse, a false green.
|
|
250
|
+
*/
|
|
251
|
+
export function shouldRetry(classification) {
|
|
252
|
+
return classification.retryable;
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=classify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.js","sourceRoot":"","sources":["../src/classify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The quarantine codemod.
|
|
3
|
+
*
|
|
4
|
+
* Writes `@quarantine` into a test's own tag array rather than maintaining a
|
|
5
|
+
* side-list of quarantined tests. A suite that already excludes that tag needs
|
|
6
|
+
* no new runtime machinery, the state is greppable, it travels with the code
|
|
7
|
+
* in review, and deleting the test deletes the quarantine.
|
|
8
|
+
*
|
|
9
|
+
* WHY THIS LIVES IN @aplaytest/flaky AND NOT @aplaytest/core: ts-morph bundles the
|
|
10
|
+
* TypeScript compiler. `core` is imported by the reporter, which runs inside
|
|
11
|
+
* every test worker — loading a compiler there would be exactly the overhead
|
|
12
|
+
* that makes people switch the tool off. `flaky` is never on that path. When
|
|
13
|
+
* the heal engine needs the same plumbing, extract it to its own package
|
|
14
|
+
* rather than pushing it down into core.
|
|
15
|
+
*/
|
|
16
|
+
export declare const QUARANTINE_TAG = "@quarantine";
|
|
17
|
+
export type CodemodStatus = 'applied' | 'already-tagged' | 'not-found' | 'ambiguous' | 'parameterised';
|
|
18
|
+
export interface CodemodResult {
|
|
19
|
+
readonly status: CodemodStatus;
|
|
20
|
+
readonly file: string;
|
|
21
|
+
readonly before: string | null;
|
|
22
|
+
readonly after: string | null;
|
|
23
|
+
/** Human-readable explanation, safe to print verbatim. */
|
|
24
|
+
readonly message: string;
|
|
25
|
+
/** 1-indexed line of the matched test, when one was found. */
|
|
26
|
+
readonly line: number | null;
|
|
27
|
+
}
|
|
28
|
+
export interface QuarantineCodemodInput {
|
|
29
|
+
readonly file: string;
|
|
30
|
+
readonly testTitle: string;
|
|
31
|
+
/** Disambiguates when a title appears more than once in the file. */
|
|
32
|
+
readonly line?: number;
|
|
33
|
+
readonly tag?: string;
|
|
34
|
+
/** Lines rendered as a leading block comment above the test. */
|
|
35
|
+
readonly comment?: readonly string[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Apply the quarantine tag. Pure with respect to disk — the caller decides
|
|
39
|
+
* whether to write `result.after`, so a dry run and a real run take exactly
|
|
40
|
+
* the same code path.
|
|
41
|
+
*/
|
|
42
|
+
export declare function quarantineCodemod(sourceText: string, input: QuarantineCodemodInput): CodemodResult;
|
|
43
|
+
/** Remove the tag again — the release path. */
|
|
44
|
+
export declare function releaseCodemod(sourceText: string, input: Omit<QuarantineCodemodInput, 'comment'>): CodemodResult;
|
|
45
|
+
//# sourceMappingURL=codemod.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codemod.d.ts","sourceRoot":"","sources":["../src/codemod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,eAAO,MAAM,cAAc,gBAAgB,CAAC;AAE5C,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,gBAAgB,GAChB,WAAW,GACX,WAAW,GACX,eAAe,CAAC;AAEpB,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAuJD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,sBAAsB,GAC5B,aAAa,CAqGf;AAED,+CAA+C;AAC/C,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,IAAI,CAAC,sBAAsB,EAAE,SAAS,CAAC,GAC7C,aAAa,CAiFf"}
|