@appforge-ci/core 0.2.3 → 0.3.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.
Files changed (60) hide show
  1. package/dist/agent-readiness.d.ts +232 -1
  2. package/dist/agent-readiness.d.ts.map +1 -1
  3. package/dist/agent-readiness.js +294 -1
  4. package/dist/agent-readiness.js.map +1 -1
  5. package/dist/agent-readiness.test.js +253 -19
  6. package/dist/agent-readiness.test.js.map +1 -1
  7. package/dist/api-client.d.ts +42 -3
  8. package/dist/api-client.d.ts.map +1 -1
  9. package/dist/api-client.js +27 -0
  10. package/dist/api-client.js.map +1 -1
  11. package/dist/api-client.test.js +22 -0
  12. package/dist/api-client.test.js.map +1 -1
  13. package/dist/attestation.d.ts +265 -0
  14. package/dist/attestation.d.ts.map +1 -0
  15. package/dist/attestation.js +327 -0
  16. package/dist/attestation.js.map +1 -0
  17. package/dist/attestation.test.d.ts +2 -0
  18. package/dist/attestation.test.d.ts.map +1 -0
  19. package/dist/attestation.test.js +218 -0
  20. package/dist/attestation.test.js.map +1 -0
  21. package/dist/build-vpn.d.ts +10 -0
  22. package/dist/build-vpn.d.ts.map +1 -0
  23. package/dist/build-vpn.js +23 -0
  24. package/dist/build-vpn.js.map +1 -0
  25. package/dist/index.d.ts +5 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +5 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/schemas.d.ts +342 -7
  30. package/dist/schemas.d.ts.map +1 -1
  31. package/dist/schemas.js +92 -2
  32. package/dist/schemas.js.map +1 -1
  33. package/dist/types.d.ts +149 -0
  34. package/dist/types.d.ts.map +1 -1
  35. package/dist/types.js.map +1 -1
  36. package/dist/vpn-egress-presets.d.ts +142 -0
  37. package/dist/vpn-egress-presets.d.ts.map +1 -0
  38. package/dist/vpn-egress-presets.js +318 -0
  39. package/dist/vpn-egress-presets.js.map +1 -0
  40. package/dist/vpn-egress-presets.test.d.ts +2 -0
  41. package/dist/vpn-egress-presets.test.d.ts.map +1 -0
  42. package/dist/vpn-egress-presets.test.js +210 -0
  43. package/dist/vpn-egress-presets.test.js.map +1 -0
  44. package/dist/vpn-egress.d.ts +57 -0
  45. package/dist/vpn-egress.d.ts.map +1 -0
  46. package/dist/vpn-egress.js +126 -0
  47. package/dist/vpn-egress.js.map +1 -0
  48. package/dist/vpn-egress.test.d.ts +2 -0
  49. package/dist/vpn-egress.test.d.ts.map +1 -0
  50. package/dist/vpn-egress.test.js +133 -0
  51. package/dist/vpn-egress.test.js.map +1 -0
  52. package/dist/vpn-ssh.d.ts +45 -0
  53. package/dist/vpn-ssh.d.ts.map +1 -0
  54. package/dist/vpn-ssh.js +108 -0
  55. package/dist/vpn-ssh.js.map +1 -0
  56. package/dist/vpn-ssh.test.d.ts +2 -0
  57. package/dist/vpn-ssh.test.d.ts.map +1 -0
  58. package/dist/vpn-ssh.test.js +75 -0
  59. package/dist/vpn-ssh.test.js.map +1 -0
  60. package/package.json +1 -1
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Egress policy settings for a VPN tunnel (migrations/0045): the versioned
3
+ * preset registry, validation of what an org admin stores, and the server-side
4
+ * expansion into the concrete `egress` block the helper enforces
5
+ * (`VpnJobConfig.egress`, vpn-egress.ts).
6
+ *
7
+ * Pure and runtime-agnostic: the API Worker, the lease-job Worker, the
8
+ * dashboard and the CLI all import it, so they apply the same rules and report
9
+ * the same problems.
10
+ *
11
+ * Presets never reach the helper. The control plane expands a tunnel's preset
12
+ * ids plus its custom hosts into one host list; the helper hashes THAT list
13
+ * (`vpnEgressPolicyHash`) and echoes the hash, so the evidence names the exact
14
+ * hosts a build ran under even if a preset's contents change in a later release.
15
+ */
16
+ import { canonicalVpnEgressPolicy, normalizeEgressHost, validateVpnEgressPolicy, VPN_EGRESS_DEFAULT_PORT, VPN_EGRESS_MAX_PORTS_PER_RULE, } from "./vpn-egress.js";
17
+ /** Version of the preset registry below. Bumped whenever a preset's hosts
18
+ * change; recorded nowhere per tunnel (the policy hash is the record). */
19
+ export const VPN_EGRESS_PRESETS_VERSION = 1;
20
+ /** At most this many presets on one tunnel (the registry is smaller; this bounds the stored JSON). */
21
+ export const VPN_EGRESS_MAX_PRESETS = 16;
22
+ /** At most this many custom `egressAllow` entries on one tunnel. With the presets
23
+ * the expanded list stays far below the helper's 512-rule limit. */
24
+ export const VPN_EGRESS_MAX_CUSTOM_RULES = 100;
25
+ /**
26
+ * The registry. Hostnames only, exact or `*.suffix`, port 443 unless a rule says
27
+ * otherwise (the helper's default). The first four presets come from the hosts
28
+ * one cold build of a sample Flutter app contacted (Android debug APK and iOS
29
+ * simulator build, empty Gradle/pub/CocoaPods caches); `npm` and `github` are the
30
+ * standard hosts of those services and were not part of that measurement.
31
+ *
32
+ * These lists are a starting point, not a guarantee that a build works: an
33
+ * app-specific host (Firebase, Sentry, App Store Connect, Google Play, a private
34
+ * registry) is not here, and a customer will see refusals until they add it as a
35
+ * custom entry. For a regulated setup the better answer is an internal mirror
36
+ * reached through the tunnel, with no presets at all.
37
+ */
38
+ export const VPN_EGRESS_PRESETS = [
39
+ {
40
+ id: "flutter",
41
+ label: "Flutter",
42
+ description: "The Flutter and Dart tools: pub packages and the Flutter engine artifacts. A Flutter app for Android also needs the Android / Gradle preset, and one for iOS needs iOS / CocoaPods.",
43
+ rules: [{ host: "pub.dev" }, { host: "storage.googleapis.com", sharedTenancy: true }],
44
+ },
45
+ {
46
+ id: "android-gradle",
47
+ label: "Android / Gradle",
48
+ description: "Google's Maven repository and Android SDK downloads, Maven Central, the Gradle plugin portal and the Gradle distribution (which is downloaded through GitHub release assets).",
49
+ rules: [
50
+ { host: "dl.google.com" },
51
+ { host: "repo.maven.apache.org" },
52
+ { host: "plugins.gradle.org" },
53
+ { host: "plugins-artifacts.gradle.org" },
54
+ { host: "services.gradle.org" },
55
+ { host: "github.com", sharedTenancy: true },
56
+ { host: "release-assets.githubusercontent.com", sharedTenancy: true },
57
+ ],
58
+ },
59
+ {
60
+ id: "ios-cocoapods",
61
+ label: "iOS / CocoaPods",
62
+ description: "The CocoaPods CDN and its jsDelivr mirror. Most pods download their source from GitHub, so a project with pods usually needs the GitHub preset too.",
63
+ rules: [{ host: "cdn.cocoapods.org" }, { host: "cdn.jsdelivr.net", sharedTenancy: true }],
64
+ },
65
+ {
66
+ id: "npm",
67
+ label: "npm / Yarn",
68
+ description: "The public npm registry and the Yarn registry alias.",
69
+ rules: [{ host: "registry.npmjs.org" }, { host: "registry.yarnpkg.com" }],
70
+ },
71
+ {
72
+ id: "github",
73
+ label: "GitHub (https)",
74
+ description: "git over https to github.com, source archives and release downloads. GitHub is shared by every tenant, so this also lets a build upload to any repository it holds a token for.",
75
+ rules: [
76
+ { host: "github.com", sharedTenancy: true },
77
+ { host: "codeload.github.com", sharedTenancy: true },
78
+ { host: "objects.githubusercontent.com", sharedTenancy: true },
79
+ { host: "raw.githubusercontent.com", sharedTenancy: true },
80
+ { host: "release-assets.githubusercontent.com", sharedTenancy: true },
81
+ ],
82
+ },
83
+ ];
84
+ const PRESET_ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
85
+ export function findVpnEgressPreset(id) {
86
+ return typeof id === "string" ? VPN_EGRESS_PRESETS.find((p) => p.id === id) : undefined;
87
+ }
88
+ /** The ids of the given presets whose hosts include a shared-tenancy host. */
89
+ export function sharedTenancyPresetIds(presetIds) {
90
+ return presetIds.filter((id) => findVpnEgressPreset(id)?.rules.some((r) => r.sharedTenancy === true));
91
+ }
92
+ /** The compliance modes of a tunnel (`VpnTunnel.complianceMode`). */
93
+ export const VPN_COMPLIANCE_MODES = ["standard", "regulated"];
94
+ /**
95
+ * Parses one `host` or `host:port` entry as typed in the CLI or dashboard. The
96
+ * host rules (no IP literal, no bare `*`, ...) are checked by
97
+ * `validateEgressAllow`; this only splits the port off and refuses what could
98
+ * not be a single `host[:port]` (a scheme, a path, an IPv6 literal).
99
+ */
100
+ export function parseEgressAllowEntry(text) {
101
+ const t = text.trim();
102
+ const m = /^([^:/\s]+)(?::(\d{1,5}))?$/.exec(t);
103
+ if (!m)
104
+ return { error: `"${shorten(t)}" is not host or host:port (no scheme, path or IPv6 literal)` };
105
+ const host = m[1];
106
+ if (m[2] === undefined)
107
+ return { rule: { host } };
108
+ const port = Number(m[2]);
109
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
110
+ return { error: `"${shorten(t)}": the port must be 1-65535` };
111
+ return { rule: { host, ports: [port] } };
112
+ }
113
+ /** Parses a list of entries, merging repeated hosts (so `a.com:443` and `a.com:8443` become one rule). Returns every error. */
114
+ export function parseEgressAllowEntries(texts) {
115
+ const rules = [];
116
+ const errors = [];
117
+ for (const text of texts) {
118
+ const parsed = parseEgressAllowEntry(text);
119
+ if ("error" in parsed)
120
+ errors.push(parsed.error);
121
+ else
122
+ rules.push(parsed.rule);
123
+ }
124
+ return { rules, errors };
125
+ }
126
+ function shorten(s) {
127
+ return s.length > 60 ? `${s.slice(0, 57)}...` : s;
128
+ }
129
+ /**
130
+ * Every problem with a tunnel's custom egress entries (empty = valid). The host
131
+ * and port rules are `validateVpnEgressPolicy`'s, so the server, CLI, dashboard,
132
+ * agent and helper refuse exactly the same hosts; only the size bound and the
133
+ * field names differ. Problem strings never echo a rejected value.
134
+ */
135
+ export function validateEgressAllow(allow) {
136
+ if (!Array.isArray(allow))
137
+ return ["egressAllow: must be an array"];
138
+ if (allow.length > VPN_EGRESS_MAX_CUSTOM_RULES) {
139
+ return [`egressAllow: may contain at most ${VPN_EGRESS_MAX_CUSTOM_RULES} entries`];
140
+ }
141
+ return validateVpnEgressPolicy({ allow }).map((p) => p.replace(/^egress\.allow/, "egressAllow"));
142
+ }
143
+ /** Every problem with a tunnel's preset ids (empty = valid). An unknown id is refused, never ignored. */
144
+ export function validateEgressPresets(presets) {
145
+ if (!Array.isArray(presets))
146
+ return ["egressPresets: must be an array"];
147
+ if (presets.length > VPN_EGRESS_MAX_PRESETS) {
148
+ return [`egressPresets: may contain at most ${VPN_EGRESS_MAX_PRESETS} presets`];
149
+ }
150
+ const problems = [];
151
+ presets.forEach((id, i) => {
152
+ if (typeof id !== "string" || !PRESET_ID.test(id))
153
+ problems.push(`egressPresets[${i}]: must be a preset id`);
154
+ else if (!findVpnEgressPreset(id)) {
155
+ problems.push(`egressPresets[${i}]: unknown preset (known: ${VPN_EGRESS_PRESETS.map((p) => p.id).join(", ")})`);
156
+ }
157
+ });
158
+ return problems;
159
+ }
160
+ /** Sorted, de-duplicated preset ids: the form that is stored. Call only on validated input. */
161
+ export function normalizeEgressPresets(presets) {
162
+ return [...new Set(presets)].sort();
163
+ }
164
+ /**
165
+ * The merged form of a rule list: hosts lowercased, one rule per host with its
166
+ * ports unioned (default 443 made explicit), sorted by host, ports ascending.
167
+ * It is also what `canonicalVpnEgressPolicy` serialises, so a stored list in
168
+ * this form never changes meaning when hashed. Throws on an invalid host: call
169
+ * `validateEgressAllow` first.
170
+ */
171
+ export function mergeEgressRules(rules) {
172
+ const merged = new Map();
173
+ for (const rule of rules) {
174
+ const host = normalizeEgressHost(rule.host);
175
+ if (host === null)
176
+ throw new Error("invalid egress host");
177
+ let ports = merged.get(host);
178
+ if (!ports)
179
+ merged.set(host, (ports = new Set()));
180
+ for (const p of rule.ports && rule.ports.length > 0 ? rule.ports : [VPN_EGRESS_DEFAULT_PORT])
181
+ ports.add(p);
182
+ }
183
+ return [...merged.keys()].sort().map((host) => ({ host, ports: [...merged.get(host)].sort((a, b) => a - b) }));
184
+ }
185
+ /** The stored form of custom entries: merged, with the helper's per-rule port limit applied by validation first. */
186
+ export function normalizeEgressAllow(allow) {
187
+ return mergeEgressRules(allow);
188
+ }
189
+ /** The rules the given (already validated) preset ids contribute, copied. */
190
+ function presetRules(ids) {
191
+ const rules = [];
192
+ for (const id of ids) {
193
+ for (const r of findVpnEgressPreset(id).rules)
194
+ rules.push({ host: r.host, ...(r.ports ? { ports: [...r.ports] } : {}) });
195
+ }
196
+ return rules;
197
+ }
198
+ /**
199
+ * Every problem with the egress part of a tunnel's settings, in one list.
200
+ *
201
+ * Beyond the per-list checks it validates the RESULT: the presets' hosts and the
202
+ * custom entries merged exactly as `expandVpnEgressPolicy` merges them (ports
203
+ * unioned per host), because that merged policy is what the helper receives and
204
+ * what its limits apply to. A preset host plus a custom entry for the same host
205
+ * can push it past the helper's per-rule port limit even when each list is fine
206
+ * on its own; catching that here (not at the first lease) is what keeps a saved
207
+ * setting from failing every build of the org.
208
+ */
209
+ export function validateEgressSettings(settings) {
210
+ const problems = [];
211
+ if (settings.egressPresets !== undefined)
212
+ problems.push(...validateEgressPresets(settings.egressPresets));
213
+ if (settings.egressAllow !== undefined)
214
+ problems.push(...validateEgressAllow(settings.egressAllow));
215
+ if (problems.length > 0)
216
+ return problems;
217
+ const presets = Array.isArray(settings.egressPresets) ? settings.egressPresets : [];
218
+ const custom = Array.isArray(settings.egressAllow) ? settings.egressAllow : [];
219
+ const merged = mergeEgressRules([...presetRules(presets), ...custom]);
220
+ merged.forEach((r) => {
221
+ if ((r.ports?.length ?? 0) > VPN_EGRESS_MAX_PORTS_PER_RULE) {
222
+ problems.push(`${r.host} lists more than ${VPN_EGRESS_MAX_PORTS_PER_RULE} ports once its presets and entries are merged`);
223
+ }
224
+ });
225
+ // Whatever else the helper would refuse in the merged policy (e.g. its total rule limit).
226
+ if (problems.length === 0) {
227
+ problems.push(...validateVpnEgressPolicy({ allow: merged }).map((p) => `merged egress policy: ${p.replace(/^egress\.allow/, "allow")}`));
228
+ }
229
+ return problems;
230
+ }
231
+ /**
232
+ * The concrete policy for a tunnel: the union of its presets' hosts and its
233
+ * custom entries, merged and sorted (so the same settings always give the same
234
+ * bytes, and so the same hash). Throws on an unknown preset, an invalid host or
235
+ * an oversized result: the lease route treats a throw as "the tunnel
236
+ * configuration could not be prepared" and fails the build closed rather than
237
+ * run it with a weaker policy than the customer set.
238
+ */
239
+ export function expandVpnEgressPolicy(settings) {
240
+ const problems = validateEgressSettings({ egressPresets: [...settings.egressPresets], egressAllow: [...settings.egressAllow] });
241
+ if (problems.length > 0)
242
+ throw new Error(`invalid egress settings: ${problems.join("; ")}`);
243
+ const rules = [...presetRules(settings.egressPresets), ...settings.egressAllow];
244
+ const policy = { allow: mergeEgressRules(rules) };
245
+ // Final guard: whatever was built must be a policy the helper accepts.
246
+ canonicalVpnEgressPolicy(policy);
247
+ return policy;
248
+ }
249
+ function parseStoredArray(text, what) {
250
+ let parsed;
251
+ try {
252
+ parsed = typeof text === "string" ? JSON.parse(text) : null;
253
+ }
254
+ catch {
255
+ parsed = null;
256
+ }
257
+ if (!Array.isArray(parsed))
258
+ throw new Error(`${what} is not a JSON array`);
259
+ return parsed;
260
+ }
261
+ /**
262
+ * Reads the two JSON columns of a tunnel row for enforcement. Strict, unlike a
263
+ * display parse: a column that is not a JSON array throws instead of reading as
264
+ * empty, because an empty allow-list is the MOST restrictive policy but an
265
+ * empty preset list would silently drop what the customer allowed, and either
266
+ * way a corrupt row must stop the build rather than guess.
267
+ */
268
+ export function egressSettingsFromRow(row) {
269
+ const presets = parseStoredArray(row.egressPresets, "egressPresets");
270
+ const allow = parseStoredArray(row.egressAllow, "egressAllow");
271
+ return { egressPresets: presets, egressAllow: allow };
272
+ }
273
+ /** Lenient read for display: unusable entries are dropped. Never use it to enforce. */
274
+ export function egressSettingsForDisplay(row) {
275
+ const arr = (text) => {
276
+ try {
277
+ const v = JSON.parse(text);
278
+ return Array.isArray(v) ? v : [];
279
+ }
280
+ catch {
281
+ return [];
282
+ }
283
+ };
284
+ const egressPresets = arr(row.egressPresets).filter((v) => typeof v === "string");
285
+ const egressAllow = arr(row.egressAllow).flatMap((v) => {
286
+ if (v === null || typeof v !== "object")
287
+ return [];
288
+ const { host, ports } = v;
289
+ if (typeof host !== "string")
290
+ return [];
291
+ const okPorts = Array.isArray(ports) ? ports.filter((p) => typeof p === "number") : [];
292
+ return [{ host, ...(okPorts.length > 0 ? { ports: okPorts } : {}) }];
293
+ });
294
+ return { egressPresets, egressAllow };
295
+ }
296
+ /**
297
+ * The text the dashboard and CLI show for one rule: `host` for the default port,
298
+ * `host:port` per extra port.
299
+ */
300
+ export function formatEgressRule(rule) {
301
+ const ports = rule.ports && rule.ports.length > 0 ? rule.ports : [VPN_EGRESS_DEFAULT_PORT];
302
+ return ports.map((p) => (p === VPN_EGRESS_DEFAULT_PORT ? rule.host : `${rule.host}:${p}`));
303
+ }
304
+ /**
305
+ * What strict egress does and does not give, in the words every surface (CLI,
306
+ * dashboard, docs/vpn.md) uses. It is only the enforcement that exists today:
307
+ * the helper's proxy refuses unlisted destinations. Nothing here may claim more
308
+ * until the build sandbox ships.
309
+ */
310
+ export const VPN_EGRESS_LIMITS = [
311
+ "Strict egress limits where the helper's proxy connects. It does not stop a process that ignores the proxy settings (xcodebuild, raw sockets, UDP, a tool that opens its own connection): builds still run as the same user on the Mac with direct network access, and a kernel build sandbox that would close that is not available yet.",
312
+ "It limits destinations, not content. Hosts shared by many tenants (GitHub, Google Cloud Storage, jsDelivr) also let a build upload data to anyone's repository or bucket there, so it is not data-loss prevention.",
313
+ "The presets are starting points from one sample build. App-specific hosts (Firebase, Sentry, App Store Connect, Google Play, private registries) are not included, so expect refusals until you add them. Where you can, prefer an internal mirror reached through the tunnel and no presets.",
314
+ "Builds of a strict tunnel run only on Macs that report the egress-policy-v1 capability (an up-to-date agent); until one is available they wait in the queue.",
315
+ ];
316
+ /** The extra sentence for `complianceMode = 'regulated'`. */
317
+ export const VPN_REGULATED_NOTE = "Regulated mode additionally requires a Mac that reports kernel-enforced build isolation. No Mac does yet, so builds of a regulated tunnel wait in the queue; it is not a compliance certification.";
318
+ //# sourceMappingURL=vpn-egress-presets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vpn-egress-presets.js","sourceRoot":"","sources":["../src/vpn-egress-presets.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=vpn-egress-presets.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vpn-egress-presets.test.d.ts","sourceRoot":"","sources":["../src/vpn-egress-presets.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,210 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { createHash } from "node:crypto";
4
+ import { createVpnTunnelSchema, updateVpnTunnelSchema, canonicalVpnEgressPolicy, egressSettingsForDisplay, egressSettingsFromRow, expandVpnEgressPolicy, findVpnEgressPreset, formatEgressRule, mergeEgressRules, normalizeEgressHost, normalizeEgressPresets, parseEgressAllowEntries, parseEgressAllowEntry, sharedTenancyPresetIds, validateEgressAllow, validateEgressPresets, validateEgressSettings, validateVpnEgressPolicy, vpnEgressPolicyHash, VPN_EGRESS_MAX_CUSTOM_RULES, VPN_EGRESS_MAX_PRESETS, VPN_EGRESS_MAX_RULES, VPN_EGRESS_PRESETS, } from "./index.js";
5
+ const sha = (s) => createHash("sha256").update(s).digest("hex");
6
+ test("registry: ids are unique and well-formed; every host passes the helper's own host rules; no bare *, no IP literal, no Unicode", () => {
7
+ assert.ok(VPN_EGRESS_PRESETS.length >= 5);
8
+ assert.equal(new Set(VPN_EGRESS_PRESETS.map((p) => p.id)).size, VPN_EGRESS_PRESETS.length);
9
+ for (const p of VPN_EGRESS_PRESETS) {
10
+ assert.match(p.id, /^[a-z0-9][a-z0-9-]*$/);
11
+ assert.ok(p.label.length > 0 && p.description.length > 0 && p.rules.length > 0, p.id);
12
+ for (const r of p.rules) {
13
+ assert.equal(normalizeEgressHost(r.host), r.host, `${p.id}: ${r.host} must already be in normal form`);
14
+ assert.notEqual(r.host, "*");
15
+ assert.doesNotMatch(r.host, /^[0-9.]+$/);
16
+ for (const port of r.ports ?? [])
17
+ assert.ok(Number.isInteger(port) && port >= 1 && port <= 65535);
18
+ }
19
+ // Each preset is a valid helper policy on its own.
20
+ assert.deepEqual(validateVpnEgressPolicy({ allow: p.rules.map(({ host, ports }) => ({ host, ...(ports ? { ports: [...ports] } : {}) })) }), [], p.id);
21
+ }
22
+ });
23
+ test("registry: the hosts the Seatbelt spike observed in a cold Flutter android + ios build are all covered by the presets", () => {
24
+ const observed = [
25
+ "dl.google.com",
26
+ "repo.maven.apache.org",
27
+ "plugins.gradle.org",
28
+ "plugins-artifacts.gradle.org",
29
+ "services.gradle.org",
30
+ "storage.googleapis.com",
31
+ "pub.dev",
32
+ "github.com",
33
+ "release-assets.githubusercontent.com",
34
+ "cdn.cocoapods.org",
35
+ "cdn.jsdelivr.net",
36
+ ];
37
+ const policy = expandVpnEgressPolicy({ egressPresets: ["flutter", "android-gradle", "ios-cocoapods"], egressAllow: [] });
38
+ const hosts = new Set(policy.allow.map((r) => r.host));
39
+ for (const h of observed)
40
+ assert.ok(hosts.has(h), `${h} missing`);
41
+ // ...and the presets add nothing the sample build did not need.
42
+ assert.deepEqual([...hosts].sort(), [...observed].sort());
43
+ });
44
+ test("registry: shared-tenancy hosts are flagged, so the UI can warn about the exfiltration channel", () => {
45
+ assert.deepEqual(sharedTenancyPresetIds(["flutter", "npm", "github", "ios-cocoapods", "android-gradle"]).sort(), [
46
+ "android-gradle",
47
+ "flutter",
48
+ "github",
49
+ "ios-cocoapods",
50
+ ]);
51
+ assert.deepEqual(sharedTenancyPresetIds(["npm"]), []);
52
+ assert.deepEqual(sharedTenancyPresetIds(["no-such-preset"]), []);
53
+ const github = findVpnEgressPreset("github");
54
+ assert.ok(github.rules.every((r) => r.sharedTenancy === true));
55
+ });
56
+ test("golden: expanding settings gives these exact canonical bytes and hash (a preset change must be deliberate)", async () => {
57
+ const policy = expandVpnEgressPolicy({
58
+ egressPresets: ["npm", "flutter"],
59
+ egressAllow: [{ host: "Git.Corp.Example.com", ports: [8443] }, { host: "*.corp.example.com" }, { host: "pub.dev", ports: [8080] }],
60
+ });
61
+ const canonical = '{"v":1,"allow":[' +
62
+ '{"host":"*.corp.example.com","ports":[443]},' +
63
+ '{"host":"git.corp.example.com","ports":[8443]},' +
64
+ '{"host":"pub.dev","ports":[443,8080]},' +
65
+ '{"host":"registry.npmjs.org","ports":[443]},' +
66
+ '{"host":"registry.yarnpkg.com","ports":[443]},' +
67
+ '{"host":"storage.googleapis.com","ports":[443]}]}';
68
+ assert.equal(canonicalVpnEgressPolicy(policy), canonical);
69
+ assert.equal(await vpnEgressPolicyHash(policy), sha(canonical));
70
+ // The expansion is already in the merged form, so canonicalising it again changes nothing.
71
+ assert.deepEqual(mergeEgressRules(policy.allow), policy.allow);
72
+ });
73
+ test("golden: the full android + flutter + cocoapods policy is pinned", async () => {
74
+ const policy = expandVpnEgressPolicy({ egressPresets: ["ios-cocoapods", "android-gradle", "flutter"], egressAllow: [] });
75
+ const canonical = '{"v":1,"allow":[' +
76
+ '{"host":"cdn.cocoapods.org","ports":[443]},{"host":"cdn.jsdelivr.net","ports":[443]},{"host":"dl.google.com","ports":[443]},' +
77
+ '{"host":"github.com","ports":[443]},{"host":"plugins-artifacts.gradle.org","ports":[443]},{"host":"plugins.gradle.org","ports":[443]},' +
78
+ '{"host":"pub.dev","ports":[443]},{"host":"release-assets.githubusercontent.com","ports":[443]},{"host":"repo.maven.apache.org","ports":[443]},' +
79
+ '{"host":"services.gradle.org","ports":[443]},{"host":"storage.googleapis.com","ports":[443]}]}';
80
+ assert.equal(canonicalVpnEgressPolicy(policy), canonical);
81
+ assert.equal(await vpnEgressPolicyHash(policy), sha(canonical));
82
+ });
83
+ test("expansion is order- and duplicate-insensitive, and no settings at all is the tunnel-only policy", async () => {
84
+ const a = expandVpnEgressPolicy({ egressPresets: ["npm", "flutter", "npm"], egressAllow: [{ host: "a.example.com" }, { host: "A.example.com", ports: [443] }] });
85
+ const b = expandVpnEgressPolicy({ egressPresets: ["flutter", "npm"], egressAllow: [{ host: "a.example.com", ports: [443] }] });
86
+ assert.deepEqual(a, b);
87
+ assert.deepEqual(expandVpnEgressPolicy({ egressPresets: [], egressAllow: [] }), { allow: [] });
88
+ assert.equal(canonicalVpnEgressPolicy({ allow: [] }), '{"v":1,"allow":[]}');
89
+ });
90
+ test("expansion refuses what the helper would: an unknown preset, a bad host, an IP literal, a bare *", () => {
91
+ assert.throws(() => expandVpnEgressPolicy({ egressPresets: ["nope"], egressAllow: [] }), /unknown preset/);
92
+ for (const host of ["*", "10.0.0.1", "2130706433", "0x7f.1", "localhost", "a.com.", "*.com", "a b.com", "ünï.example.com", "a.example.com:443", "*.*.example.com"]) {
93
+ assert.throws(() => expandVpnEgressPolicy({ egressPresets: [], egressAllow: [{ host }] }), /invalid egress settings/, host);
94
+ }
95
+ });
96
+ test("validateEgressAllow: bare *, IP literals, scheme/path/port in the host, trailing dot, Unicode and bad ports are all rejected, and no rejected value is echoed", () => {
97
+ const bad = ["*", "10.0.0.1", "127.0.0.1", "169.254.169.254", "::1", "2130706433", "0x7f.0.0.1", "https://a.example.com", "a.example.com/x", "a.example.com.", "a.example.com:443", "ünï.example.com", "", "*.example.*"];
98
+ for (const host of bad) {
99
+ const problems = validateEgressAllow([{ host }]);
100
+ assert.ok(problems.length > 0, `${JSON.stringify(host)} must be rejected`);
101
+ for (const p of problems) {
102
+ assert.match(p, /^egressAllow\[0\]/);
103
+ if (host.length > 3)
104
+ assert.ok(!p.includes(host), `problem echoes the value: ${p}`);
105
+ }
106
+ }
107
+ assert.ok(validateEgressAllow([{ host: "a.example.com", ports: [0] }]).length > 0);
108
+ assert.ok(validateEgressAllow([{ host: "a.example.com", ports: [65536] }]).length > 0);
109
+ assert.ok(validateEgressAllow([{ host: "a.example.com", ports: [1.5] }]).length > 0);
110
+ assert.ok(validateEgressAllow([{ host: "a.example.com", ports: "443" }]).length > 0);
111
+ assert.ok(validateEgressAllow("a.example.com").length > 0);
112
+ assert.ok(validateEgressAllow([null]).length > 0);
113
+ assert.deepEqual(validateEgressAllow([{ host: "a.example.com" }, { host: "*.b.example.com", ports: [443, 8443] }]), []);
114
+ });
115
+ test("size limits: too many custom entries, too many presets, too many ports (also once merged)", () => {
116
+ const many = Array.from({ length: VPN_EGRESS_MAX_CUSTOM_RULES + 1 }, (_, i) => ({ host: `h${i}.example.com` }));
117
+ assert.match(validateEgressAllow(many)[0], /at most 100 entries/);
118
+ assert.deepEqual(validateEgressAllow(many.slice(0, VPN_EGRESS_MAX_CUSTOM_RULES)), []);
119
+ assert.match(validateEgressPresets(Array.from({ length: VPN_EGRESS_MAX_PRESETS + 1 }, () => "npm"))[0], /at most 16 presets/);
120
+ const ports17 = Array.from({ length: 17 }, (_, i) => 1000 + i);
121
+ assert.ok(validateEgressAllow([{ host: "a.example.com", ports: ports17 }]).length > 0);
122
+ // 16 ports in each of two entries for the same host merge to 17+ distinct ports.
123
+ const first16 = Array.from({ length: 16 }, (_, i) => 1000 + i);
124
+ const entries = [
125
+ { host: "a.example.com", ports: first16 },
126
+ { host: "A.example.com", ports: [2000] },
127
+ ];
128
+ assert.deepEqual(validateEgressAllow(entries), []);
129
+ assert.match(validateEgressSettings({ egressAllow: entries })[0], /more than 16 ports once its presets and entries are merged/);
130
+ assert.throws(() => expandVpnEgressPolicy({ egressPresets: [], egressAllow: entries }), /merged/);
131
+ assert.ok(VPN_EGRESS_MAX_CUSTOM_RULES + VPN_EGRESS_PRESETS.reduce((n, p) => n + p.rules.length, 0) < VPN_EGRESS_MAX_RULES);
132
+ });
133
+ test("validateEgressPresets: unknown, malformed and non-array input is refused", () => {
134
+ assert.deepEqual(validateEgressPresets(["flutter", "npm"]), []);
135
+ assert.match(validateEgressPresets(["bogus"])[0], /unknown preset/);
136
+ assert.match(validateEgressPresets(["Flutter"])[0], /must be a preset id/);
137
+ assert.match(validateEgressPresets([1])[0], /must be a preset id/);
138
+ assert.match(validateEgressPresets("flutter")[0], /must be an array/);
139
+ assert.deepEqual(normalizeEgressPresets(["npm", "flutter", "npm"]), ["flutter", "npm"]);
140
+ });
141
+ test("parseEgressAllowEntry: host, host:port, and what is not an entry", () => {
142
+ assert.deepEqual(parseEgressAllowEntry("repo.example.com"), { rule: { host: "repo.example.com" } });
143
+ assert.deepEqual(parseEgressAllowEntry(" repo.example.com:8443 "), { rule: { host: "repo.example.com", ports: [8443] } });
144
+ assert.deepEqual(parseEgressAllowEntry("*.example.com:443"), { rule: { host: "*.example.com", ports: [443] } });
145
+ for (const bad of ["", "https://a.example.com", "a.example.com/x", "[::1]:443", "::1", "a.example.com:", "a.example.com:0", "a.example.com:70000", "a.example.com:12ab", "a b"]) {
146
+ assert.ok("error" in parseEgressAllowEntry(bad), JSON.stringify(bad));
147
+ }
148
+ const { rules, errors } = parseEgressAllowEntries(["a.example.com:443", "a.example.com:8443", "https://x"]);
149
+ assert.equal(rules.length, 2);
150
+ assert.equal(errors.length, 1);
151
+ assert.deepEqual(mergeEgressRules(rules), [{ host: "a.example.com", ports: [443, 8443] }]);
152
+ });
153
+ test("a long rejected entry is shortened in its error", () => {
154
+ const parsed = parseEgressAllowEntry(`https://${"a".repeat(300)}.example.com`);
155
+ assert.ok("error" in parsed && parsed.error.length < 140);
156
+ });
157
+ test("egressSettingsFromRow is strict: a corrupt column throws instead of reading as empty", () => {
158
+ assert.deepEqual(egressSettingsFromRow({ egressPresets: '["npm"]', egressAllow: '[{"host":"a.example.com"}]' }), {
159
+ egressPresets: ["npm"],
160
+ egressAllow: [{ host: "a.example.com" }],
161
+ });
162
+ assert.throws(() => egressSettingsFromRow({ egressPresets: "not json", egressAllow: "[]" }), /egressPresets/);
163
+ assert.throws(() => egressSettingsFromRow({ egressPresets: "[]", egressAllow: '{"host":"a"}' }), /egressAllow/);
164
+ assert.throws(() => egressSettingsFromRow({ egressPresets: "[]", egressAllow: null }), /egressAllow/);
165
+ // A well-formed JSON array of the wrong thing is refused at expansion, never trusted.
166
+ const s = egressSettingsFromRow({ egressPresets: "[1]", egressAllow: '["x"]' });
167
+ assert.throws(() => expandVpnEgressPolicy(s), /invalid egress settings/);
168
+ });
169
+ test("egressSettingsForDisplay is lenient and never throws", () => {
170
+ assert.deepEqual(egressSettingsForDisplay({ egressPresets: "nope", egressAllow: "nope" }), { egressPresets: [], egressAllow: [] });
171
+ assert.deepEqual(egressSettingsForDisplay({ egressPresets: '["npm",3]', egressAllow: '[{"host":"a.example.com","ports":[443,"x"]},7,{"ports":[1]}]' }), {
172
+ egressPresets: ["npm"],
173
+ egressAllow: [{ host: "a.example.com", ports: [443] }],
174
+ });
175
+ });
176
+ test("formatEgressRule shows the default port as the bare host", () => {
177
+ assert.deepEqual(formatEgressRule({ host: "a.example.com" }), ["a.example.com"]);
178
+ assert.deepEqual(formatEgressRule({ host: "a.example.com", ports: [443, 8443] }), ["a.example.com", "a.example.com:8443"]);
179
+ });
180
+ test("a preset host plus a custom entry for it is validated as merged: 443 + 16 custom ports is refused at save time, not at the first lease", () => {
181
+ const sixteen = Array.from({ length: 16 }, (_, i) => 8001 + i);
182
+ const settings = { egressPresets: ["github"], egressAllow: [{ host: "github.com", ports: sixteen }] };
183
+ const problems = validateEgressSettings(settings);
184
+ assert.equal(problems.length, 1);
185
+ assert.match(problems[0], /github\.com lists more than 16 ports once its presets and entries are merged/);
186
+ // The same settings are what expansion (the lease path) refuses, so save and lease now agree.
187
+ assert.throws(() => expandVpnEgressPolicy(settings), /merged/);
188
+ // 15 custom ports + the preset's 443 is exactly 16: accepted by both.
189
+ const fifteen = sixteen.slice(0, 15);
190
+ assert.deepEqual(validateEgressSettings({ ...settings, egressAllow: [{ host: "github.com", ports: fifteen }] }), []);
191
+ assert.doesNotThrow(() => expandVpnEgressPolicy({ ...settings, egressAllow: [{ host: "github.com", ports: fifteen }] }));
192
+ // Restating a preset's own port adds nothing.
193
+ assert.deepEqual(validateEgressSettings({ egressPresets: ["github"], egressAllow: [{ host: "github.com", ports: [443] }] }), []);
194
+ });
195
+ test("the tunnel schemas themselves reject bare *, IP literals and a bad preset, so a caller using them alone is safe", () => {
196
+ const base = {
197
+ name: "HQ",
198
+ gatewayEndpoint: "vpn.example.com:51820",
199
+ gatewayPublicKey: "A".repeat(43) + "=",
200
+ lanCidrs: ["10.20.0.0/16"],
201
+ };
202
+ assert.equal(createVpnTunnelSchema.safeParse({ ...base, egressAllow: [{ host: "a.example.com" }], egressPresets: ["npm"] }).success, true);
203
+ for (const host of ["*", "10.0.0.1"]) {
204
+ assert.equal(createVpnTunnelSchema.safeParse({ ...base, egressAllow: [{ host }] }).success, false, host);
205
+ assert.equal(updateVpnTunnelSchema.safeParse({ egressAllow: [{ host }] }).success, false, host);
206
+ }
207
+ assert.equal(updateVpnTunnelSchema.safeParse({ egressPresets: ["no-such-preset"] }).success, false);
208
+ assert.equal(updateVpnTunnelSchema.safeParse({ strictEgress: true }).success, true);
209
+ });
210
+ //# sourceMappingURL=vpn-egress-presets.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vpn-egress-presets.test.js","sourceRoot":"","sources":["../src/vpn-egress-presets.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,wBAAwB,EACxB,wBAAwB,EACxB,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,EACnB,2BAA2B,EAC3B,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AAEpB,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAExE,IAAI,CAAC,+HAA+H,EAAE,GAAG,EAAE;IACzI,MAAM,CAAC,EAAE,CAAC,kBAAkB,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IAC1C,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC3F,KAAK,MAAM,CAAC,IAAI,kBAAkB,EAAE,CAAC;QACnC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,sBAAsB,CAAC,CAAC;QAC3C,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QACtF,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;YACxB,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,iCAAiC,CAAC,CAAC;YACvG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAC7B,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;YACzC,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE;gBAAE,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,CAAC,CAAC;QACpG,CAAC;QACD,mDAAmD;QACnD,MAAM,CAAC,SAAS,CAAC,uBAAuB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IACxJ,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sHAAsH,EAAE,GAAG,EAAE;IAChI,MAAM,QAAQ,GAAG;QACf,eAAe;QACf,uBAAuB;QACvB,oBAAoB;QACpB,8BAA8B;QAC9B,qBAAqB;QACrB,wBAAwB;QACxB,SAAS;QACT,YAAY;QACZ,sCAAsC;QACtC,mBAAmB;QACnB,kBAAkB;KACnB,CAAC;IACF,MAAM,MAAM,GAAG,qBAAqB,CAAC,EAAE,aAAa,EAAE,CAAC,SAAS,EAAE,gBAAgB,EAAE,eAAe,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;IACzH,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACvD,KAAK,MAAM,CAAC,IAAI,QAAQ;QAAE,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IAClE,gEAAgE;IAChE,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AAC5D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,+FAA+F,EAAE,GAAG,EAAE;IACzG,MAAM,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,gBAAgB,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE;QAC/G,gBAAgB;QAChB,SAAS;QACT,QAAQ;QACR,eAAe;KAChB,CAAC,CAAC;IACH,MAAM,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACtD,MAAM,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC,gBAAgB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjE,MAAM,MAAM,GAAG,mBAAmB,CAAC,QAAQ,CAAE,CAAC;IAC9C,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,KAAK,IAAI,CAAC,CAAC,CAAC;AACjE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,4GAA4G,EAAE,KAAK,IAAI,EAAE;IAC5H,MAAM,MAAM,GAAG,qBAAqB,CAAC;QACnC,aAAa,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC;QACjC,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;KACnI,CAAC,CAAC;IACH,MAAM,SAAS,GACb,kBAAkB;QAClB,8CAA8C;QAC9C,iDAAiD;QACjD,wCAAwC;QACxC,8CAA8C;QAC9C,gDAAgD;QAChD,mDAAmD,CAAC;IACtD,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC,CAAC;IAC1D,MAAM,CAAC,KAAK,CAAC,MAAM,mBAAmB,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;IAChE,2FAA2F;IAC3F,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;AACjE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;IACjF,MAAM,MAAM,GAAG,qBAAqB,CAAC,EAAE,aAAa,EAAE,CAAC,eAAe,EAAE,gBAAgB,EAAE,SAAS,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;IACzH,MAAM,SAAS,GACb,kBAAkB;QAClB,8HAA8H;QAC9H,wIAAwI;QACxI,gJAAgJ;QAChJ,gGAAgG,CAAC;IACnG,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC,CAAC;IAC1D,MAAM,CAAC,KAAK,CAAC,MAAM,mBAAmB,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;AAClE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iGAAiG,EAAE,KAAK,IAAI,EAAE;IACjH,MAAM,CAAC,GAAG,qBAAqB,CAAC,EAAE,aAAa,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACjK,MAAM,CAAC,GAAG,qBAAqB,CAAC,EAAE,aAAa,EAAE,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/H,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACvB,MAAM,CAAC,SAAS,CAAC,qBAAqB,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/F,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,EAAE,oBAAoB,CAAC,CAAC;AAC9E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iGAAiG,EAAE,GAAG,EAAE;IAC3G,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,EAAE,aAAa,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,EAAE,gBAAgB,CAAC,CAAC;IAC3G,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,iBAAiB,CAAC,EAAE,CAAC;QACnK,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,yBAAyB,EAAE,IAAI,CAAC,CAAC;IAC9H,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,+JAA+J,EAAE,GAAG,EAAE;IACzK,MAAM,GAAG,GAAG,CAAC,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,iBAAiB,EAAE,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,EAAE,EAAE,aAAa,CAAC,CAAC;IAC1N,KAAK,MAAM,IAAI,IAAI,GAAG,EAAE,CAAC;QACvB,MAAM,QAAQ,GAAG,mBAAmB,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAC3E,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YACzB,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,mBAAmB,CAAC,CAAC;YACrC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;gBAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,6BAA6B,CAAC,EAAE,CAAC,CAAC;QACtF,CAAC;IACH,CAAC;IACD,MAAM,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACnF,MAAM,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACvF,MAAM,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACrF,MAAM,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,KAAc,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC9F,MAAM,CAAC,EAAE,CAAC,mBAAmB,CAAC,eAAe,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC3D,MAAM,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAClD,MAAM,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC1H,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,2FAA2F,EAAE,GAAG,EAAE;IACrG,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,2BAA2B,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;IAChH,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,EAAE,qBAAqB,CAAC,CAAC;IACnE,MAAM,CAAC,SAAS,CAAC,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,2BAA2B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACtF,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,sBAAsB,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAE,EAAE,oBAAoB,CAAC,CAAC;IAC/H,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IAC/D,MAAM,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACvF,iFAAiF;IACjF,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAG;QACd,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,EAAE;QACzC,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE;KACzC,CAAC;IACF,MAAM,CAAC,SAAS,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;IACnD,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAE,EAAE,4DAA4D,CAAC,CAAC;IACjI,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;IAClG,MAAM,CAAC,EAAE,CAAC,2BAA2B,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,oBAAoB,CAAC,CAAC;AAC7H,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0EAA0E,EAAE,GAAG,EAAE;IACpF,MAAM,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChE,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAE,EAAE,gBAAgB,CAAC,CAAC;IACrE,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAE,EAAE,qBAAqB,CAAC,CAAC;IAC5E,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,EAAE,qBAAqB,CAAC,CAAC;IACpE,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAE,EAAE,kBAAkB,CAAC,CAAC;IACvE,MAAM,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;AAC1F,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,kEAAkE,EAAE,GAAG,EAAE;IAC5E,MAAM,CAAC,SAAS,CAAC,qBAAqB,CAAC,kBAAkB,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAC;IACpG,MAAM,CAAC,SAAS,CAAC,qBAAqB,CAAC,yBAAyB,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IAC1H,MAAM,CAAC,SAAS,CAAC,qBAAqB,CAAC,mBAAmB,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;IAChH,KAAK,MAAM,GAAG,IAAI,CAAC,EAAE,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,WAAW,EAAE,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC;QAChL,MAAM,CAAC,EAAE,CAAC,OAAO,IAAI,qBAAqB,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IACxE,CAAC;IACD,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,uBAAuB,CAAC,CAAC,mBAAmB,EAAE,oBAAoB,EAAE,WAAW,CAAC,CAAC,CAAC;IAC5G,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC9B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC/B,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;AAC7F,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iDAAiD,EAAE,GAAG,EAAE;IAC3D,MAAM,MAAM,GAAG,qBAAqB,CAAC,WAAW,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC/E,MAAM,CAAC,EAAE,CAAC,OAAO,IAAI,MAAM,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;AAC5D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sFAAsF,EAAE,GAAG,EAAE;IAChG,MAAM,CAAC,SAAS,CAAC,qBAAqB,CAAC,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,4BAA4B,EAAE,CAAC,EAAE;QAC/G,aAAa,EAAE,CAAC,KAAK,CAAC;QACtB,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;KACzC,CAAC,CAAC;IACH,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,EAAE,eAAe,CAAC,CAAC;IAC9G,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,EAAE,aAAa,CAAC,CAAC;IAChH,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,WAAW,EAAE,IAAa,EAAE,CAAC,EAAE,aAAa,CAAC,CAAC;IAC/G,sFAAsF;IACtF,MAAM,CAAC,GAAG,qBAAqB,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;IAChF,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,CAAC,CAAC,EAAE,yBAAyB,CAAC,CAAC;AAC3E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sDAAsD,EAAE,GAAG,EAAE;IAChE,MAAM,CAAC,SAAS,CAAC,wBAAwB,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;IACnI,MAAM,CAAC,SAAS,CAAC,wBAAwB,CAAC,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,8DAA8D,EAAE,CAAC,EAAE;QACtJ,aAAa,EAAE,CAAC,KAAK,CAAC;QACtB,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;KACvD,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0DAA0D,EAAE,GAAG,EAAE;IACpE,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;IACjF,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,eAAe,EAAE,oBAAoB,CAAC,CAAC,CAAC;AAC7H,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,wIAAwI,EAAE,GAAG,EAAE;IAClJ,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IAC/D,MAAM,QAAQ,GAAG,EAAE,aAAa,EAAE,CAAC,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IACtG,MAAM,QAAQ,GAAG,sBAAsB,CAAC,QAAQ,CAAC,CAAC;IAClD,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACjC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAE,EAAE,8EAA8E,CAAC,CAAC;IAC3G,8FAA8F;IAC9F,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC/D,sEAAsE;IACtE,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrC,MAAM,CAAC,SAAS,CAAC,sBAAsB,CAAC,EAAE,GAAG,QAAQ,EAAE,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IACrH,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,EAAE,GAAG,QAAQ,EAAE,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACzH,8CAA8C;IAC9C,MAAM,CAAC,SAAS,CAAC,sBAAsB,CAAC,EAAE,aAAa,EAAE,CAAC,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AACnI,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iHAAiH,EAAE,GAAG,EAAE;IAC3H,MAAM,IAAI,GAAG;QACX,IAAI,EAAE,IAAI;QACV,eAAe,EAAE,uBAAuB;QACxC,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,GAAG;QACtC,QAAQ,EAAE,CAAC,cAAc,CAAC;KAC3B,CAAC;IACF,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,SAAS,CAAC,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC3I,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,EAAE,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,SAAS,CAAC,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QACzG,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAClG,CAAC;IACD,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,SAAS,CAAC,EAAE,aAAa,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACpG,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AACtF,CAAC,CAAC,CAAC"}