@osqd/bothandlerjs 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.
- package/CHANGELOG.md +516 -0
- package/LICENSE +102 -0
- package/README.md +308 -0
- package/bin/bothandlerjs.mjs +14 -0
- package/dist/actions/index.d.ts +22 -0
- package/dist/actions/types.d.ts +59 -0
- package/dist/adapters/fastify.d.ts +43 -0
- package/dist/adapters/fetch.d.ts +51 -0
- package/dist/adapters/index.cjs +723 -0
- package/dist/adapters/index.cjs.map +1 -0
- package/dist/adapters/index.d.ts +8 -0
- package/dist/adapters/index.js +692 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/koa.d.ts +27 -0
- package/dist/adapters/node.d.ts +29 -0
- package/dist/adapters/shared.d.ts +39 -0
- package/dist/audit.d.ts +172 -0
- package/dist/challenge/index.d.ts +176 -0
- package/dist/challenge/language.d.ts +57 -0
- package/dist/challenge/page.d.ts +42 -0
- package/dist/challenge/pow.d.ts +33 -0
- package/dist/challenge/token.d.ts +72 -0
- package/dist/cli.cjs +13388 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +13358 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/index.cjs +139 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.ts +63 -0
- package/dist/client/index.js +112 -0
- package/dist/client/index.js.map +1 -0
- package/dist/config.d.ts +316 -0
- package/dist/core.d.ts +443 -0
- package/dist/corpus/adversarial.d.ts +2 -0
- package/dist/corpus/advertising-email.d.ts +2 -0
- package/dist/corpus/ai-crawlers.d.ts +2 -0
- package/dist/corpus/benign-bots.d.ts +2 -0
- package/dist/corpus/cdn-gateways.d.ts +2 -0
- package/dist/corpus/cookies.d.ts +49 -0
- package/dist/corpus/crawlers-regional.d.ts +2 -0
- package/dist/corpus/crawlers-vertical.d.ts +2 -0
- package/dist/corpus/headers.d.ts +403 -0
- package/dist/corpus/humans-apps.d.ts +2 -0
- package/dist/corpus/humans-browsers.d.ts +2 -0
- package/dist/corpus/humans.d.ts +2 -0
- package/dist/corpus/index.cjs +4196 -0
- package/dist/corpus/index.cjs.map +1 -0
- package/dist/corpus/index.d.ts +37 -0
- package/dist/corpus/index.js +4132 -0
- package/dist/corpus/index.js.map +1 -0
- package/dist/corpus/infrastructure.d.ts +17 -0
- package/dist/corpus/libraries-extended.d.ts +2 -0
- package/dist/corpus/ranges.d.ts +31 -0
- package/dist/corpus/reputation.d.ts +21 -0
- package/dist/corpus/runner.d.ts +130 -0
- package/dist/corpus/schema.d.ts +185 -0
- package/dist/corpus/tooling.d.ts +2 -0
- package/dist/corpus/unwanted.d.ts +2 -0
- package/dist/crawler-ranges.d.ts +113 -0
- package/dist/dashboard/client.generated.d.ts +7 -0
- package/dist/dashboard/feed.d.ts +186 -0
- package/dist/dashboard/index.d.ts +5 -0
- package/dist/dashboard/page.d.ts +51 -0
- package/dist/dashboard/parse-request.d.ts +28 -0
- package/dist/dashboard/preview.d.ts +45 -0
- package/dist/dashboard/server.d.ts +70 -0
- package/dist/dashboard/types.d.ts +714 -0
- package/dist/detectors/accept-signature.d.ts +6 -0
- package/dist/detectors/browsing-coherence.d.ts +44 -0
- package/dist/detectors/cadence.d.ts +28 -0
- package/dist/detectors/clearance.d.ts +25 -0
- package/dist/detectors/client-hints.d.ts +19 -0
- package/dist/detectors/client-signals.d.ts +27 -0
- package/dist/detectors/crawl-breadth.d.ts +28 -0
- package/dist/detectors/crawler-verification.d.ts +39 -0
- package/dist/detectors/fetch-metadata.d.ts +20 -0
- package/dist/detectors/header-integrity.d.ts +26 -0
- package/dist/detectors/header-order.d.ts +27 -0
- package/dist/detectors/identity-rotation.d.ts +34 -0
- package/dist/detectors/index.d.ts +56 -0
- package/dist/detectors/ip-intelligence.d.ts +30 -0
- package/dist/detectors/known-bots.d.ts +94 -0
- package/dist/detectors/probe-signature.d.ts +50 -0
- package/dist/detectors/rate-anomaly.d.ts +36 -0
- package/dist/detectors/self-identified.d.ts +33 -0
- package/dist/detectors/session-integrity.d.ts +24 -0
- package/dist/detectors/tls-fingerprint.d.ts +34 -0
- package/dist/detectors/trap.d.ts +98 -0
- package/dist/detectors/types.d.ts +70 -0
- package/dist/detectors/ua-coherence.d.ts +32 -0
- package/dist/evidence.d.ts +45 -0
- package/dist/facts.d.ts +46 -0
- package/dist/index.cjs +9600 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +9444 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/async.d.ts +16 -0
- package/dist/internal/clock.d.ts +17 -0
- package/dist/internal/crypto.d.ts +26 -0
- package/dist/internal/dns.d.ts +100 -0
- package/dist/internal/emitter.d.ts +18 -0
- package/dist/internal/http.d.ts +22 -0
- package/dist/internal/ip.d.ts +73 -0
- package/dist/internal/lru.d.ts +34 -0
- package/dist/internal/matcher.d.ts +41 -0
- package/dist/internal/pattern.d.ts +14 -0
- package/dist/internal/ua.d.ts +92 -0
- package/dist/metrics.d.ts +120 -0
- package/dist/notify/hub.d.ts +70 -0
- package/dist/notify/index.d.ts +7 -0
- package/dist/notify/redact.d.ts +48 -0
- package/dist/notify/sinks.d.ts +62 -0
- package/dist/notify/types.d.ts +56 -0
- package/dist/policy/index.d.ts +7 -0
- package/dist/policy/match.d.ts +20 -0
- package/dist/policy/policy.d.ts +123 -0
- package/dist/policy/presets.d.ts +161 -0
- package/dist/policy/types.d.ts +155 -0
- package/dist/robots.d.ts +83 -0
- package/dist/state.d.ts +161 -0
- package/dist/stores/index.d.ts +5 -0
- package/dist/stores/memory.d.ts +33 -0
- package/dist/stores/redis.d.ts +59 -0
- package/dist/stores/types.d.ts +37 -0
- package/dist/types.d.ts +250 -0
- package/docs/challenge/index.md +152 -0
- package/docs/challenge/localisation.md +105 -0
- package/docs/concepts/actors.md +113 -0
- package/docs/concepts/evidence.md +133 -0
- package/docs/concepts/the-guard.md +121 -0
- package/docs/concepts/threat-model.md +120 -0
- package/docs/concepts/verdicts.md +108 -0
- package/docs/course/01-first-assessment.md +178 -0
- package/docs/course/02-proof-and-suspicion.md +164 -0
- package/docs/course/03-verdicts-and-scores.md +160 -0
- package/docs/course/04-the-guard.md +200 -0
- package/docs/course/05-detectors.md +178 -0
- package/docs/course/06-identity.md +161 -0
- package/docs/course/07-actors.md +178 -0
- package/docs/course/08-traps.md +183 -0
- package/docs/course/09-rules.md +224 -0
- package/docs/course/10-actions-and-presets.md +216 -0
- package/docs/course/11-the-challenge.md +227 -0
- package/docs/course/12-going-live.md +211 -0
- package/docs/course/13-operating-it.md +238 -0
- package/docs/course/14-scaling.md +195 -0
- package/docs/course/15-extending.md +259 -0
- package/docs/course/16-proving-it.md +266 -0
- package/docs/course/index.md +112 -0
- package/docs/design/decisions.md +216 -0
- package/docs/detection/client-signals.md +96 -0
- package/docs/detection/detectors.md +376 -0
- package/docs/detection/index.md +134 -0
- package/docs/detection/signatures.md +111 -0
- package/docs/detection/verification.md +123 -0
- package/docs/detection/writing-a-detector.md +145 -0
- package/docs/index.md +100 -0
- package/docs/integration/adapters.md +138 -0
- package/docs/integration/client-ip.md +114 -0
- package/docs/integration/index.md +55 -0
- package/docs/integration/stores.md +76 -0
- package/docs/operations/audit.md +116 -0
- package/docs/operations/dashboard.md +725 -0
- package/docs/operations/index.md +101 -0
- package/docs/operations/metrics.md +74 -0
- package/docs/operations/notifications.md +72 -0
- package/docs/operations/runtime-changes.md +118 -0
- package/docs/policy/actions.md +196 -0
- package/docs/policy/index.md +84 -0
- package/docs/policy/presets.md +191 -0
- package/docs/policy/robots.md +128 -0
- package/docs/policy/rules.md +134 -0
- package/docs/reference/api.md +126 -0
- package/docs/reference/configuration.md +175 -0
- package/docs/start/choosing-a-policy.md +127 -0
- package/docs/start/first-integration.md +127 -0
- package/docs/start/installation.md +63 -0
- package/docs/start/upgrading.md +72 -0
- package/docs/testing/cli.md +119 -0
- package/docs/testing/corpus.md +129 -0
- package/docs/testing/index.md +61 -0
- package/docs/testing/replay.md +102 -0
- package/docs/testing/try-it.md +189 -0
- package/package.json +145 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/internal/clock.ts","../../src/internal/http.ts","../../src/internal/ip.ts","../../src/facts.ts","../../src/corpus/index.ts","../../src/corpus/headers.ts","../../src/corpus/schema.ts","../../src/corpus/ranges.ts","../../src/corpus/ai-crawlers.ts","../../src/corpus/adversarial.ts","../../src/corpus/benign-bots.ts","../../src/corpus/crawlers-regional.ts","../../src/corpus/crawlers-vertical.ts","../../src/corpus/advertising-email.ts","../../src/corpus/cookies.ts","../../src/corpus/cdn-gateways.ts","../../src/corpus/humans-browsers.ts","../../src/corpus/humans-apps.ts","../../src/corpus/humans.ts","../../src/corpus/infrastructure.ts","../../src/corpus/reputation.ts","../../src/corpus/libraries-extended.ts","../../src/corpus/tooling.ts","../../src/corpus/unwanted.ts","../../src/corpus/runner.ts"],"sourcesContent":["/**\n * Injectable time. Every stateful detector reads `now()` from here rather than\n * calling `Date.now()`, which is what makes rate, cadence and expiry logic testable\n * without sleeping and without flaky wall-clock assumptions.\n */\nexport interface Clock {\n now(): number;\n}\n\nexport const systemClock: Clock = { now: () => Date.now() };\n\n/** Test double: time only advances when you say so. */\nexport class ManualClock implements Clock {\n constructor(private current = 0) {}\n now(): number {\n return this.current;\n }\n advance(ms: number): void {\n this.current += ms;\n }\n set(ms: number): void {\n this.current = ms;\n }\n}\n","/** Cookie parsing and serialisation, plus small header helpers. No dependencies. */\n\n/** Longest Cookie header we will parse. Beyond this it is a payload, not a session. */\nconst MAX_COOKIE_HEADER = 8192;\nconst MAX_COOKIES = 64;\n\n/**\n * Parses a `Cookie` header into a null-prototype bag.\n *\n * Null-prototype matters: a request carrying `__proto__=x` would otherwise hit the\n * prototype setter on a plain object and vanish, and any later `cookies.constructor`\n * lookup would find `Object`'s rather than `undefined`. Neither is a vulnerability by\n * itself; both are the kind of surprise that becomes one.\n */\nexport function parseCookies(header: string | undefined): Record<string, string> {\n const cookies: Record<string, string> = Object.create(null) as Record<string, string>;\n if (header === undefined || header.length === 0) return cookies;\n const source = header.length > MAX_COOKIE_HEADER ? header.slice(0, MAX_COOKIE_HEADER) : header;\n\n let count = 0;\n for (const pair of source.split(\";\")) {\n if (count >= MAX_COOKIES) break;\n const equals = pair.indexOf(\"=\");\n if (equals <= 0) continue;\n const name = pair.slice(0, equals).trim();\n if (name.length === 0) continue;\n const rawValue = pair.slice(equals + 1).trim();\n const value = rawValue.startsWith('\"') && rawValue.endsWith('\"') ? rawValue.slice(1, -1) : rawValue;\n try {\n cookies[name] = decodeURIComponent(value);\n } catch {\n // A malformed percent-escape is not worth rejecting the whole header over.\n cookies[name] = value;\n }\n count++;\n }\n return cookies;\n}\n\nexport interface CookieOptions {\n maxAgeMs?: number;\n path?: string;\n domain?: string;\n secure?: boolean;\n httpOnly?: boolean;\n sameSite?: \"Lax\" | \"Strict\" | \"None\";\n}\n\n/** Builds a `Set-Cookie` value. Rejects names and values that would let a caller inject attributes. */\nexport function serializeCookie(name: string, value: string, options: CookieOptions = {}): string {\n if (!/^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/.test(name)) throw new TypeError(`Invalid cookie name: ${name}`);\n const parts = [`${name}=${encodeURIComponent(value)}`];\n if (options.maxAgeMs !== undefined) parts.push(`Max-Age=${Math.floor(options.maxAgeMs / 1000)}`);\n parts.push(`Path=${options.path ?? \"/\"}`);\n if (options.domain !== undefined) {\n if (!/^[A-Za-z0-9.-]+$/.test(options.domain)) throw new TypeError(`Invalid cookie domain: ${options.domain}`);\n parts.push(`Domain=${options.domain}`);\n }\n if (options.secure !== false) parts.push(\"Secure\");\n if (options.httpOnly !== false) parts.push(\"HttpOnly\");\n // `SameSite=None` without `Secure` is rejected by browsers, so it is never a valid\n // combination to emit — fail loudly here rather than shipping a cookie nothing keeps.\n const sameSite = options.sameSite ?? \"Lax\";\n if (sameSite === \"None\" && options.secure === false) {\n throw new TypeError(\"SameSite=None requires Secure\");\n }\n parts.push(`SameSite=${sameSite}`);\n return parts.join(\"; \");\n}\n\n/** Joins multi-value headers the way the rest of the library expects to see them. */\nexport function joinHeaderValue(value: string | string[] | undefined): string | undefined {\n if (value === undefined) return undefined;\n return Array.isArray(value) ? value.join(\", \") : value;\n}\n","/**\n * IP parsing, normalisation and CIDR matching, with no dependencies.\n *\n * Everything works on raw byte arrays rather than strings, because string\n * comparison of IPs is a classic source of bypasses: `::ffff:127.0.0.1`,\n * `0177.0.0.1` and `127.0.0.001` are all the same host to the network stack but\n * three different strings to a naive allowlist.\n */\n\n/** An IP as bytes: 4 for IPv4, 16 for IPv6. IPv4-mapped IPv6 is folded to 4. */\nexport type IpBytes = Uint8Array;\n\n/**\n * Parses an IPv4 or IPv6 literal into bytes, or `null` if it is not a valid\n * address. Deliberately strict: no octal, no hex, no shorthand octets, no zone\n * ids. Anything ambiguous is rejected rather than guessed at.\n */\nexport function parseIp(value: string): IpBytes | null {\n const input = value.trim();\n if (input.length === 0 || input.length > 45) return null;\n // Bracketed form from a Host/Forwarded header: [2001:db8::1]\n const bare = input.startsWith(\"[\") && input.endsWith(\"]\") ? input.slice(1, -1) : input;\n if (bare.includes(\":\")) return parseIpv6(bare);\n return parseIpv4(bare);\n}\n\nfunction parseIpv4(value: string): IpBytes | null {\n const parts = value.split(\".\");\n if (parts.length !== 4) return null;\n const bytes = new Uint8Array(4);\n for (let i = 0; i < 4; i++) {\n const part = parts[i]!;\n // Reject empty, over-long, non-digit and leading-zero forms outright.\n if (part.length === 0 || part.length > 3) return null;\n if (!/^\\d+$/.test(part)) return null;\n if (part.length > 1 && part[0] === \"0\") return null;\n const n = Number(part);\n if (n > 255) return null;\n bytes[i] = n;\n }\n return bytes;\n}\n\nfunction parseIpv6(value: string): IpBytes | null {\n // Strip a zone id (`%eth0`) — it is a local routing detail, not part of identity.\n const withoutZone = value.split(\"%\")[0]!;\n const doubleColon = withoutZone.indexOf(\"::\");\n if (doubleColon !== withoutZone.lastIndexOf(\"::\")) return null;\n\n const [headText, tailText] =\n doubleColon === -1\n ? [withoutZone, \"\"]\n : [withoutZone.slice(0, doubleColon), withoutZone.slice(doubleColon + 2)];\n\n const head = headText.length > 0 ? headText.split(\":\") : [];\n const tail = tailText.length > 0 ? tailText.split(\":\") : [];\n\n // A trailing dotted-quad (`::ffff:192.0.2.1`) occupies the last two groups.\n let embedded: IpBytes | null = null;\n const groups = [...head, ...tail];\n const last = groups[groups.length - 1];\n if (last !== undefined && last.includes(\".\")) {\n embedded = parseIpv4(last);\n if (!embedded) return null;\n if (tail.length > 0) tail.pop();\n else head.pop();\n }\n\n const groupCount = head.length + tail.length + (embedded ? 2 : 0);\n if (doubleColon === -1 ? groupCount !== 8 : groupCount > 7) return null;\n\n const bytes = new Uint8Array(16);\n let offset = 0;\n for (const group of head) {\n if (!writeGroup(bytes, offset, group)) return null;\n offset += 2;\n }\n // The gap the `::` stands for, left as the zeroes the array already holds.\n offset = 16 - tail.length * 2 - (embedded ? 4 : 0);\n for (const group of tail) {\n if (!writeGroup(bytes, offset, group)) return null;\n offset += 2;\n }\n if (embedded) bytes.set(embedded, 12);\n\n // Fold IPv4-mapped (::ffff:0:0/96) down to a plain v4 address so that one host\n // has exactly one representation regardless of which stack accepted it.\n if (isIpv4Mapped(bytes)) return bytes.slice(12, 16);\n return bytes;\n}\n\nfunction writeGroup(bytes: Uint8Array, offset: number, group: string): boolean {\n if (group.length === 0 || group.length > 4 || !/^[0-9a-fA-F]+$/.test(group)) return false;\n const n = Number.parseInt(group, 16);\n bytes[offset] = n >> 8;\n bytes[offset + 1] = n & 0xff;\n return true;\n}\n\nfunction isIpv4Mapped(bytes: Uint8Array): boolean {\n for (let i = 0; i < 10; i++) if (bytes[i] !== 0) return false;\n return bytes[10] === 0xff && bytes[11] === 0xff;\n}\n\n/** Canonical string form, so the same host always produces the same actor key. */\nexport function formatIp(bytes: IpBytes): string {\n if (bytes.length === 4) return `${bytes[0]}.${bytes[1]}.${bytes[2]}.${bytes[3]}`;\n const groups: string[] = [];\n for (let i = 0; i < 16; i += 2) groups.push(((bytes[i]! << 8) | bytes[i + 1]!).toString(16));\n // RFC 5952: compress the longest run of zero groups, leftmost on a tie.\n let bestStart = -1;\n let bestLen = 0;\n for (let i = 0; i < 8; i++) {\n if (groups[i] !== \"0\") continue;\n let j = i;\n while (j < 8 && groups[j] === \"0\") j++;\n if (j - i > bestLen) {\n bestLen = j - i;\n bestStart = i;\n }\n i = j;\n }\n if (bestLen < 2) return groups.join(\":\");\n return `${groups.slice(0, bestStart).join(\":\")}::${groups.slice(bestStart + bestLen).join(\":\")}`;\n}\n\n/** Normalises any accepted spelling of an address to its canonical form. Returns `null` if unparseable. */\nexport function normalizeIp(value: string): string | null {\n const bytes = parseIp(value);\n return bytes ? formatIp(bytes) : null;\n}\n\nexport interface Cidr {\n readonly bytes: IpBytes;\n readonly prefix: number;\n readonly source: string;\n}\n\n/** Parses `\"10.0.0.0/8\"`, `\"2001:db8::/32\"`, or a bare address (treated as a /32 or /128). */\nexport function parseCidr(value: string): Cidr | null {\n const slash = value.lastIndexOf(\"/\");\n const addressText = slash === -1 ? value : value.slice(0, slash);\n const bytes = parseIp(addressText);\n if (!bytes) return null;\n const maxPrefix = bytes.length * 8;\n if (slash === -1) return { bytes, prefix: maxPrefix, source: value };\n const prefixText = value.slice(slash + 1);\n if (!/^\\d{1,3}$/.test(prefixText)) return null;\n const prefix = Number(prefixText);\n if (prefix > maxPrefix) return null;\n return { bytes, prefix, source: value };\n}\n\n/** True when `ip` falls inside `cidr`. Address families never match across each other. */\nexport function cidrContains(cidr: Cidr, ip: IpBytes): boolean {\n if (cidr.bytes.length !== ip.length) return false;\n const fullBytes = cidr.prefix >> 3;\n for (let i = 0; i < fullBytes; i++) if (cidr.bytes[i] !== ip[i]) return false;\n const remainder = cidr.prefix & 7;\n if (remainder === 0) return true;\n const mask = 0xff << (8 - remainder);\n return (cidr.bytes[fullBytes]! & mask) === (ip[fullBytes]! & mask);\n}\n\n/**\n * One range plus the position it was added at, so that when several ranges match the\n * same address the answer does not depend on how the index happens to be laid out.\n */\ninterface IndexedCidr {\n readonly cidr: Cidr;\n readonly order: number;\n}\n\n/**\n * Ranges for one address family, indexed on the first two bytes.\n *\n * A linear scan is fine for the handful of CIDRs in an allowlist and catastrophic for\n * the list this library actually asks operators to supply: AWS publishes around seven\n * thousand IPv4 prefixes, GCP and Azure comparable numbers, and `datacenterRanges` is\n * documented as the place to put them. Scanning that on every request measured at\n * ~87µs — several times the cost of the entire rest of an assessment — so the size of\n * a range list silently became the dominant term in request latency.\n *\n * Bucketing by the leading bytes fixes that without changing any answer. A prefix of\n * /16 or longer pins both leading bytes, so it is reachable from exactly one bucket;\n * a /8 to /15 pins only the first; anything shorter than /8 spans buckets and stays in\n * a list checked on every lookup. Real range lists are overwhelmingly /16 and longer,\n * which is why this collapses to a handful of comparisons.\n */\nclass FamilyIndex {\n /** Prefix < 8. Spans first bytes, so it is always scanned. Realistically empty. */\n private readonly wide: IndexedCidr[] = [];\n private readonly buckets = new Map<number, { mid: IndexedCidr[]; deep: Map<number, IndexedCidr[]> }>();\n\n add(entry: IndexedCidr): void {\n const { cidr } = entry;\n if (cidr.prefix < 8) {\n this.wide.push(entry);\n return;\n }\n const first = cidr.bytes[0]!;\n let bucket = this.buckets.get(first);\n if (bucket === undefined) {\n bucket = { mid: [], deep: new Map() };\n this.buckets.set(first, bucket);\n }\n if (cidr.prefix < 16) {\n bucket.mid.push(entry);\n return;\n }\n const second = cidr.bytes[1]!;\n let deep = bucket.deep.get(second);\n if (deep === undefined) {\n deep = [];\n bucket.deep.set(second, deep);\n }\n deep.push(entry);\n }\n\n /** The earliest-added range containing `ip`, or `undefined`. */\n find(ip: IpBytes): IndexedCidr | undefined {\n let best = scan(this.wide, ip, undefined);\n const bucket = this.buckets.get(ip[0]!);\n if (bucket !== undefined) {\n best = scan(bucket.mid, ip, best);\n const deep = bucket.deep.get(ip[1]!);\n if (deep !== undefined) best = scan(deep, ip, best);\n }\n return best;\n }\n}\n\nfunction scan(entries: readonly IndexedCidr[], ip: IpBytes, best: IndexedCidr | undefined): IndexedCidr | undefined {\n let winner = best;\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i]!;\n if (winner !== undefined && entry.order > winner.order) continue;\n if (cidrContains(entry.cidr, ip)) winner = entry;\n }\n return winner;\n}\n\n/**\n * A compiled set of CIDR ranges. Building it once and reusing it keeps the hot\n * path free of string parsing — matching is a handful of byte comparisons, and stays\n * that way as the list grows into the thousands.\n */\nexport class IpRangeSet {\n private readonly v4 = new FamilyIndex();\n private readonly v6 = new FamilyIndex();\n private count = 0;\n /**\n * Every range that parsed, in the order it was added.\n *\n * Kept as plain text beside the index rather than reconstructed from it. The index\n * is shaped for lookups — bucketed by leading byte, prefix-split — and walking it\n * back into a list would be both slower and, for anything that wants to *show* the\n * set, wrong: what an operator recognises is the string they wrote, not a normalised\n * form of it. These sets are configuration-sized, so the array costs nothing.\n */\n private readonly sources: string[] = [];\n /** Ranges that failed to parse, surfaced so a typo in config is loud rather than silent. */\n readonly invalid: string[] = [];\n\n constructor(ranges: Iterable<string> = []) {\n for (const range of ranges) this.add(range);\n }\n\n add(range: string): void {\n const cidr = parseCidr(range);\n if (!cidr) {\n this.invalid.push(range);\n return;\n }\n this.sources.push(cidr.source);\n (cidr.bytes.length === 4 ? this.v4 : this.v6).add({ cidr, order: this.count++ });\n }\n\n get size(): number {\n return this.count;\n }\n\n /** The ranges in this set, as written, oldest first. What a reader can act on. */\n entries(): readonly string[] {\n return this.sources;\n }\n\n /**\n * Returns the matching range's original text, or `undefined`. Useful for explaining\n * a decision. When several ranges match, the one added first wins, so the\n * explanation does not depend on the index's internal layout.\n */\n match(ip: string | IpBytes): string | undefined {\n const bytes = typeof ip === \"string\" ? parseIp(ip) : ip;\n if (!bytes) return undefined;\n return (bytes.length === 4 ? this.v4 : this.v6).find(bytes)?.cidr.source;\n }\n\n contains(ip: string | IpBytes): boolean {\n const bytes = typeof ip === \"string\" ? parseIp(ip) : ip;\n if (!bytes) return false;\n return (bytes.length === 4 ? this.v4 : this.v6).find(bytes) !== undefined;\n }\n}\n\n/** IANA special-purpose ranges: loopback, private, link-local, CGNAT, documentation. */\nexport const SPECIAL_USE_RANGES = [\n \"0.0.0.0/8\",\n \"10.0.0.0/8\",\n \"100.64.0.0/10\",\n \"127.0.0.0/8\",\n \"169.254.0.0/16\",\n \"172.16.0.0/12\",\n \"192.0.0.0/24\",\n \"192.0.2.0/24\",\n \"192.168.0.0/16\",\n \"198.18.0.0/15\",\n \"198.51.100.0/24\",\n \"203.0.113.0/24\",\n \"224.0.0.0/4\",\n \"240.0.0.0/4\",\n \"::1/128\",\n \"fc00::/7\",\n \"fe80::/10\",\n \"2001:db8::/32\",\n] as const;\n\nconst SPECIAL_USE = new IpRangeSet(SPECIAL_USE_RANGES);\n\n/** True for loopback/private/link-local/documentation addresses — never public clients. */\nexport function isSpecialUse(ip: string): boolean {\n return SPECIAL_USE.contains(ip);\n}\n\n/**\n * Masks an address to a coarse network for rate accounting: /24 for IPv4, /64 for\n * IPv6. IPv6 clients routinely get a whole /64 to themselves and rotate the host\n * bits freely, so per-address counting is trivially defeated there.\n */\nexport function networkKey(ip: string): string {\n const bytes = parseIp(ip);\n if (!bytes) return ip;\n // Build the masked address at full width. Slicing to three bytes and formatting\n // that would produce a 3-byte array, which `formatIp` reads as IPv6.\n if (bytes.length === 4) {\n const masked = new Uint8Array([bytes[0]!, bytes[1]!, bytes[2]!, 0]);\n return `${formatIp(masked)}/24`;\n }\n const masked = new Uint8Array(16);\n masked.set(bytes.subarray(0, 8), 0);\n return `${formatIp(masked)}/64`;\n}\n","import { joinHeaderValue, parseCookies } from \"./internal/http.js\";\nimport { normalizeIp } from \"./internal/ip.js\";\nimport type { RequestFacts } from \"./types.js\";\n\n/** Longest URL we will parse. Anything beyond this is a payload, not a path. */\nconst MAX_URL_LENGTH = 8192;\n/** Cap on query parameters kept. */\nconst MAX_QUERY_PARAMS = 64;\n\nexport interface FactsInput {\n method?: string | undefined;\n /** Request target, path plus optional query — what `req.url` gives you. */\n url?: string | undefined;\n /** Raw header map. Values may be arrays; names may be any case. */\n headers: Record<string, string | string[] | undefined>;\n /**\n * Header names in wire order. Node exposes them via `req.rawHeaders` (alternating\n * name/value) — pass that array directly, or a name-only list.\n */\n rawHeaders?: readonly string[] | undefined;\n /** Socket address. Pass the *socket's* address; forwarding is resolved separately. */\n ip: string;\n timestamp?: number | undefined;\n protocol?: \"http\" | \"https\" | undefined;\n /**\n * The HTTP version of the connection **this process accepted** — Node's\n * `request.httpVersion`, not the version the client negotiated with your edge.\n *\n * The distinction has teeth. HTTP/2 forbids connection-specific headers, and\n * `header-integrity` treats one as a deterministic protocol violation. If you set\n * this from a forwarded header while the request itself arrived over HTTP/1.1 from\n * a proxy — which adds `Connection: keep-alive` — you will manufacture that\n * violation for every real browser behind that proxy, and it is a `certain` verdict,\n * so it can block. Report the connection you actually have, or leave it unset.\n */\n httpVersion?: string | undefined;\n tlsFingerprint?: string | undefined;\n /** See {@link RequestFacts.partialHeaders}. Set it when the source cannot supply every header. */\n partialHeaders?: boolean | undefined;\n extra?: Record<string, unknown> | undefined;\n}\n\n/**\n * Reduces a request to the facts detectors are allowed to see.\n *\n * Normalisation happens exactly once, here, and every detector reads the result. That\n * is partly performance — lowercasing a header map per detector would be absurd — but\n * mostly correctness: if one detector reads `req.headers['User-Agent']` and another\n * reads `req.headers['user-agent']`, they will eventually disagree about the same\n * request, and the bug will be invisible.\n *\n * Every field is bounded. Each one is attacker-controlled, and this runs on every\n * request to your site.\n */\nexport function createFacts(input: FactsInput): RequestFacts {\n const rawUrl = input.url ?? \"/\";\n const url = rawUrl.length > MAX_URL_LENGTH ? rawUrl.slice(0, MAX_URL_LENGTH) : rawUrl;\n const queryStart = url.indexOf(\"?\");\n const rawPath = queryStart === -1 ? url : url.slice(0, queryStart);\n\n const headers: Record<string, string | undefined> = Object.create(null) as Record<string, string | undefined>;\n for (const [name, value] of Object.entries(input.headers)) {\n const joined = joinHeaderValue(value);\n if (joined !== undefined) headers[name.toLowerCase()] = joined;\n }\n\n const facts: RequestFacts = {\n method: (input.method ?? \"GET\").toUpperCase(),\n path: normalizePath(rawPath),\n query: parseQuery(queryStart === -1 ? \"\" : url.slice(queryStart + 1)),\n headers,\n headerOrder: extractOrder(input.rawHeaders, headers),\n ip: normalizeIp(input.ip) ?? input.ip,\n timestamp: input.timestamp ?? Date.now(),\n };\n\n // Only parse cookies that exist. Most bot traffic carries none, and building an\n // empty bag for every one of those requests is pure garbage.\n const cookieHeader = headers[\"cookie\"];\n if (cookieHeader !== undefined) facts.cookies = parseCookies(cookieHeader);\n\n if (input.protocol !== undefined) facts.protocol = input.protocol;\n if (input.httpVersion !== undefined) facts.httpVersion = input.httpVersion;\n if (input.tlsFingerprint !== undefined) facts.tlsFingerprint = input.tlsFingerprint;\n if (input.partialHeaders === true) facts.partialHeaders = true;\n if (input.extra !== undefined) facts.extra = input.extra;\n\n return facts;\n}\n\n/**\n * Decodes and normalises a path.\n *\n * Rule matching is done on this value, so `/admin`, `/%61dmin` and `/./admin` must\n * not be three different paths as far as a policy is concerned — otherwise a rule\n * scoped to a path is trivially side-stepped by spelling it differently. Decoding is\n * single-pass: repeatedly decoding until it stops changing is how `%2525` becomes `%`\n * and how path-traversal filters get bypassed.\n */\nfunction normalizePath(rawPath: string): string {\n let path = rawPath;\n try {\n path = decodeURIComponent(rawPath);\n } catch {\n // Malformed percent-encoding. Keep the raw form: it is still a fact about the\n // request, and guessing at an intended decoding would be inventing one.\n }\n path = path.replace(/\\\\/g, \"/\").replace(/\\/{2,}/g, \"/\");\n if (!path.startsWith(\"/\")) path = `/${path}`;\n\n // Resolve `.` and `..` segments so a rule on a path prefix cannot be walked around.\n if (path.includes(\"./\")) {\n const resolved: string[] = [];\n for (const segment of path.split(\"/\")) {\n if (segment === \"\" || segment === \".\") continue;\n if (segment === \"..\") resolved.pop();\n else resolved.push(segment);\n }\n path = `/${resolved.join(\"/\")}`;\n }\n return path.length > 1 && path.endsWith(\"/\") ? path.slice(0, -1) : path;\n}\n\nfunction parseQuery(search: string): Record<string, string> {\n // Null-prototype: a literal `?__proto__=x` becomes an ordinary own key instead of\n // hitting the prototype setter and vanishing, so a detector can actually see it.\n const query: Record<string, string> = Object.create(null) as Record<string, string>;\n if (search.length === 0) return query;\n let count = 0;\n for (const [key, value] of new URLSearchParams(search)) {\n if (count++ >= MAX_QUERY_PARAMS) break;\n query[key] = value.length > 1024 ? value.slice(0, 1024) : value;\n }\n return query;\n}\n\n/**\n * Extracts wire-order header names.\n *\n * Both documented input shapes have to be told apart: Node's `rawHeaders`, which\n * alternates name and value, and a plain list of names.\n *\n * Asking whether the even-indexed entries *look like* header names cannot do it, and\n * used to get the common case backwards. Every entry of a name-only list looks like a\n * header name, so any such list of even length was read as Node-style and every second\n * name was discarded — silently halving the header order, which is a fingerprint, for\n * a shape the documentation invites callers to pass.\n *\n * The header map decides it instead. In a name-only list every entry is a header that\n * was actually received; in an alternating list the odd entries are values, which are\n * almost never also header names. That distinguishes the two even for a single-header\n * request, where any shape-based guess is ambiguous.\n */\nconst MAX_ORDERED_HEADERS = 64;\n\nfunction extractOrder(rawHeaders: readonly string[] | undefined, headers: Record<string, string | undefined>): readonly string[] {\n if (!rawHeaders || rawHeaders.length === 0) return EMPTY_ORDER;\n\n let isNodeStyle = rawHeaders.length % 2 === 0;\n if (isNodeStyle) {\n let everyEntryIsAHeader = true;\n for (let i = 0; i < rawHeaders.length; i++) {\n const entry = rawHeaders[i]!;\n const known = headers[entry.toLowerCase()] !== undefined;\n // An even entry that is neither a received header nor even a valid token cannot\n // be a name in either shape; fall back to reading the list as names rather than\n // dropping half of something unrecognised.\n if (i % 2 === 0 && !known && !isHeaderName(entry)) {\n isNodeStyle = false;\n break;\n }\n if (!known) everyEntryIsAHeader = false;\n }\n if (everyEntryIsAHeader) isNodeStyle = false;\n }\n\n // One pass, one array. The previous version walked the input three times —\n // `every`, then `filter`, then `slice().map()` — allocating at each step.\n const step = isNodeStyle ? 2 : 1;\n const order: string[] = [];\n for (let i = 0; i < rawHeaders.length && order.length < MAX_ORDERED_HEADERS; i += step) {\n order.push(rawHeaders[i]!.toLowerCase());\n }\n return order;\n}\n\nconst EMPTY_ORDER: readonly string[] = Object.freeze([]);\n\n/** RFC 9110 token characters. A scan rather than a regex: this runs once per header. */\nfunction isHeaderName(value: string): boolean {\n if (value.length === 0) return false;\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n const ok =\n (code >= 0x30 && code <= 0x39) || // 0-9\n (code >= 0x41 && code <= 0x5a) || // A-Z\n (code >= 0x61 && code <= 0x7a) || // a-z\n code === 0x21 || (code >= 0x23 && code <= 0x27) || code === 0x2a || code === 0x2b ||\n code === 0x2d || code === 0x2e || code === 0x5e || code === 0x5f || code === 0x60 ||\n code === 0x7c || code === 0x7e;\n if (!ok) return false;\n }\n return true;\n}\n","import { AI_CRAWLER_CASES } from \"./ai-crawlers.js\";\nimport { ADVERSARIAL_CASES } from \"./adversarial.js\";\nimport { BENIGN_BOT_CASES } from \"./benign-bots.js\";\nimport { REGIONAL_CRAWLER_CASES } from \"./crawlers-regional.js\";\nimport { VERTICAL_CRAWLER_CASES } from \"./crawlers-vertical.js\";\nimport { ADVERTISING_EMAIL_CASES } from \"./advertising-email.js\";\nimport { CDN_GATEWAY_CASES } from \"./cdn-gateways.js\";\nimport { HUMAN_BROWSER_CASES } from \"./humans-browsers.js\";\nimport { HUMAN_APP_CASES } from \"./humans-apps.js\";\nimport { HUMAN_CASES } from \"./humans.js\";\nimport { INFRASTRUCTURE_CASES } from \"./infrastructure.js\";\nimport { REPUTATION_CASES } from \"./reputation.js\";\nimport { EXTENDED_LIBRARY_CASES } from \"./libraries-extended.js\";\nimport { TOOLING_CASES } from \"./tooling.js\";\nimport { UNWANTED_BOT_CASES } from \"./unwanted.js\";\nimport type { Audience, TrafficCase } from \"./schema.js\";\n\nexport * from \"./schema.js\";\n// The runner, which is the entire point of publishing this entry point: \"point it at your\n// own BotHandler and it tells you how *your* policy behaves\". It was missing here — every\n// internal caller imports `./runner.js` directly, so nothing exercised the path the\n// documentation tells other people to use, and `import { runCorpus } from\n// \"@osqd/bothandlerjs/corpus\"` failed for everyone outside this repository.\nexport { runCorpus } from \"./runner.js\";\nexport type { CaseResult, RunnerOptions, Scorecard, AudienceTally } from \"./runner.js\";\nexport * from \"./headers.js\";\nexport * from \"./ranges.js\";\nexport { HUMAN_CASES } from \"./humans.js\";\nexport { HUMAN_BROWSER_CASES } from \"./humans-browsers.js\";\nexport { HUMAN_APP_CASES } from \"./humans-apps.js\";\nexport { BENIGN_BOT_CASES } from \"./benign-bots.js\";\nexport { REGIONAL_CRAWLER_CASES } from \"./crawlers-regional.js\";\nexport { VERTICAL_CRAWLER_CASES } from \"./crawlers-vertical.js\";\nexport { ADVERTISING_EMAIL_CASES } from \"./advertising-email.js\";\nexport { CDN_GATEWAY_CASES } from \"./cdn-gateways.js\";\nexport { AI_CRAWLER_CASES } from \"./ai-crawlers.js\";\nexport { UNWANTED_BOT_CASES } from \"./unwanted.js\";\nexport { TOOLING_CASES } from \"./tooling.js\";\nexport { EXTENDED_LIBRARY_CASES } from \"./libraries-extended.js\";\nexport { ADVERSARIAL_CASES } from \"./adversarial.js\";\nexport { INFRASTRUCTURE_CASES } from \"./infrastructure.js\";\nexport { REPUTATION_CASES } from \"./reputation.js\";\n\n/**\n * The corpus.\n *\n * Order matters only for readability. Every case carries its own address and its own\n * clock, so no case can influence another — which is what makes it safe to run a\n * subset, or one case on its own while debugging.\n */\nexport const CORPUS: readonly TrafficCase[] = Object.freeze([\n ...HUMAN_CASES,\n ...HUMAN_BROWSER_CASES,\n ...HUMAN_APP_CASES,\n ...BENIGN_BOT_CASES,\n ...REGIONAL_CRAWLER_CASES,\n ...VERTICAL_CRAWLER_CASES,\n ...ADVERTISING_EMAIL_CASES,\n ...CDN_GATEWAY_CASES,\n ...AI_CRAWLER_CASES,\n ...UNWANTED_BOT_CASES,\n ...TOOLING_CASES,\n ...EXTENDED_LIBRARY_CASES,\n ...ADVERSARIAL_CASES,\n ...INFRASTRUCTURE_CASES,\n ...REPUTATION_CASES,\n]);\n\n/** What each audience means, and what being wrong about it costs. */\nexport const AUDIENCE_STAKES: Readonly<Record<Audience, string>> = {\n human: \"A person. Denying one is a customer turned away, and the corpus treats it as a hard failure.\",\n \"benign-bot\": \"Automation you want. Denying one costs search ranking, share previews, or a monitor that lies about being green.\",\n \"declared-bot\": \"Honest automation. How you treat it is a business decision; the library only has to name it correctly.\",\n \"unwanted-bot\": \"Automation most sites decline. Blocking it is safe when — and only when — it declared itself.\",\n hostile: \"Scanners, forgeries and credential attacks. Denying these is the point.\",\n infrastructure: \"Your own machinery. Usually belongs in `ignorePaths` or the allowlist rather than in front of a detector.\",\n};\n\n/** Fails fast on a duplicate id, which would otherwise silently shadow a case in every report. */\nexport function assertCorpusIntegrity(cases: readonly TrafficCase[] = CORPUS): void {\n const seen = new Map<string, TrafficCase>();\n const problems: string[] = [];\n\n for (const item of cases) {\n const previous = seen.get(item.id);\n if (previous) problems.push(`duplicate id \"${item.id}\" (${previous.title} / ${item.title})`);\n seen.set(item.id, item);\n\n if (item.requests.length === 0) problems.push(`case \"${item.id}\" has no requests`);\n if (item.provenance.trim().length === 0) problems.push(`case \"${item.id}\" has no provenance`);\n if (!/^[a-z0-9][a-z0-9-]*$/.test(item.id)) problems.push(`case id \"${item.id}\" is not kebab-case`);\n }\n\n if (problems.length > 0) throw new Error(`Corpus integrity problems:\\n - ${problems.join(\"\\n - \")}`);\n}\n\nexport function casesByAudience(audience: Audience, cases: readonly TrafficCase[] = CORPUS): TrafficCase[] {\n return cases.filter((item) => item.audience === audience);\n}\n\nexport function casesByTag(tag: string, cases: readonly TrafficCase[] = CORPUS): TrafficCase[] {\n return cases.filter((item) => item.tags?.includes(tag) === true);\n}\n\n/** Every distinct category present, for reports that group by it. */\nexport function categories(cases: readonly TrafficCase[] = CORPUS): string[] {\n return [...new Set(cases.map((item) => item.category))].sort();\n}\n","import type { CaseRequest } from \"./schema.js\";\n\n/**\n * Header profiles for real clients.\n *\n * Three things here are load-bearing and easy to get wrong when writing fixtures by\n * hand.\n *\n * **Order.** Each engine emits headers in a fixed sequence that differs between\n * Chromium, Gecko and WebKit and barely changes across releases. It is one of the few\n * properties a scraper cannot fix by copying a User-Agent, so a corpus that gets the\n * order wrong is testing a client that does not exist.\n *\n * **Completeness.** A real browser sends a whole cluster together — the negotiation\n * headers, the `Sec-Fetch-*` set, the Client Hints on Chromium, a dozen cookies it\n * never asked for. Omitting one because it seemed unimportant turns a human fixture\n * into a bot fixture and quietly inverts what the case proves.\n *\n * **The optional apparatus.** Modern browsers carry a long tail of conditional\n * headers: high-entropy Client Hints once a server has asked for them, `Sec-GPC` from\n * privacy-preserving builds, `Save-Data` on metered connections, `Sec-Purpose` on\n * speculative loads, `Early-Data` on a TLS 1.3 resumption, cache validators on a\n * revisit. Each is modelled here because each is something a hand-rolled client\n * forgets, and because a corpus of only the happy path tests only the happy path.\n *\n * Sources: 2026 User-Agent lists; W3C `TR/fetch-metadata` for the `Sec-Fetch-*`\n * combinations; the UA Client Hints specification for the `Sec-CH-*` set; observed\n * request captures.\n */\n\nexport type Header = readonly [name: string, value: string];\n\n/** What kind of request this is. Decides the `Sec-Fetch-*` set and the `Accept` value. */\nexport type RequestKind =\n /** Typing a URL or following a bookmark: no referrer, user-initiated. */\n | \"navigate\"\n /** Following a link within the site. */\n | \"same-origin-navigate\"\n /** Arriving from another site. */\n | \"cross-site-navigate\"\n /** A form POST back to the same origin. */\n | \"form-post\"\n /** `fetch()` for JSON from page script. */\n | \"xhr\"\n /** A cross-origin `fetch()` that will be preflighted. */\n | \"cors\"\n /** An image, font or stylesheet. */\n | \"subresource\"\n /** A stylesheet specifically. */\n | \"stylesheet\"\n /** A script tag. */\n | \"script\"\n /** A media byte-range request from a `<video>`. */\n | \"media\"\n /** An `EventSource` stream. */\n | \"eventsource\"\n /** A `<iframe>` load. */\n | \"iframe\";\n\nexport interface ProfileOptions {\n kind?: RequestKind;\n host?: string;\n /** Cookie header value. See `cookies.ts` for realistic jars. */\n cookie?: string;\n referer?: string;\n origin?: string;\n /** Overrides the profile's default language list — a real setting people change. */\n acceptLanguage?: string;\n /** High-entropy Client Hints, sent only after a server advertises `Accept-CH`. */\n highEntropyHints?: boolean;\n /** `DNT: 1`. Still sent by a meaningful minority despite being deprecated. */\n dnt?: boolean;\n /** `Sec-GPC: 1` — Global Privacy Control, sent by Brave, DuckDuckGo and others. */\n gpc?: boolean;\n /** `Save-Data: on` — a metered or data-saver connection. */\n saveData?: boolean;\n /** Network Information hints (`RTT`, `Downlink`, `ECT`), also gated behind `Accept-CH`. */\n networkHints?: { rtt: number; downlink: number; ect: \"slow-2g\" | \"2g\" | \"3g\" | \"4g\" };\n /** Layout hints (`Viewport-Width`, `DPR`, `Width`), gated behind `Accept-CH`. */\n layoutHints?: { viewportWidth: number; dpr: number };\n /** Conditional request headers from a revisit. */\n revalidate?: { etag?: string; modifiedSince?: string };\n /** `Cache-Control: max-age=0`, which a browser sends on an explicit reload. */\n reload?: boolean;\n /** `Sec-Purpose: prefetch` plus the legacy `Purpose` header. */\n prefetch?: boolean;\n /** `Sec-Fetch-Storage-Access`, sent by recent Chromium in third-party contexts. */\n storageAccess?: \"none\" | \"active\" | \"inactive\";\n /** User preference hints, sent when the server asks for them. */\n prefers?: { colorScheme?: \"light\" | \"dark\"; reducedMotion?: \"no-preference\" | \"reduce\" };\n /** `Early-Data: 1` — a TLS 1.3 0-RTT resumption replayed by an intermediary. */\n earlyData?: boolean;\n /** Byte range for a media request. */\n range?: string;\n /** Body content type for a POST. */\n contentType?: string;\n contentLength?: number;\n}\n\nconst HOST = \"shop.example\";\n\ninterface EngineProfile {\n userAgent: string;\n acceptDocument: string;\n acceptEncoding: string;\n acceptLanguage: string;\n build: (options: ResolvedOptions, self: EngineProfile) => Header[];\n}\n\ntype ResolvedOptions = ProfileOptions & Required<Pick<ProfileOptions, \"host\" | \"kind\" | \"acceptLanguage\">>;\n\n// --- Fetch Metadata, per W3C TR/fetch-metadata -------------------------------\n\nfunction fetchMetadata(kind: RequestKind): Header[] {\n switch (kind) {\n case \"navigate\":\n return [[\"Sec-Fetch-Site\", \"none\"], [\"Sec-Fetch-Mode\", \"navigate\"], [\"Sec-Fetch-User\", \"?1\"], [\"Sec-Fetch-Dest\", \"document\"]];\n case \"same-origin-navigate\":\n return [[\"Sec-Fetch-Site\", \"same-origin\"], [\"Sec-Fetch-Mode\", \"navigate\"], [\"Sec-Fetch-User\", \"?1\"], [\"Sec-Fetch-Dest\", \"document\"]];\n case \"cross-site-navigate\":\n return [[\"Sec-Fetch-Site\", \"cross-site\"], [\"Sec-Fetch-Mode\", \"navigate\"], [\"Sec-Fetch-User\", \"?1\"], [\"Sec-Fetch-Dest\", \"document\"]];\n case \"form-post\":\n return [[\"Sec-Fetch-Site\", \"same-origin\"], [\"Sec-Fetch-Mode\", \"navigate\"], [\"Sec-Fetch-User\", \"?1\"], [\"Sec-Fetch-Dest\", \"document\"]];\n case \"xhr\":\n return [[\"Sec-Fetch-Site\", \"same-origin\"], [\"Sec-Fetch-Mode\", \"cors\"], [\"Sec-Fetch-Dest\", \"empty\"]];\n case \"cors\":\n return [[\"Sec-Fetch-Site\", \"cross-site\"], [\"Sec-Fetch-Mode\", \"cors\"], [\"Sec-Fetch-Dest\", \"empty\"]];\n case \"subresource\":\n return [[\"Sec-Fetch-Site\", \"same-origin\"], [\"Sec-Fetch-Mode\", \"no-cors\"], [\"Sec-Fetch-Dest\", \"image\"]];\n case \"stylesheet\":\n return [[\"Sec-Fetch-Site\", \"same-origin\"], [\"Sec-Fetch-Mode\", \"no-cors\"], [\"Sec-Fetch-Dest\", \"style\"]];\n case \"script\":\n return [[\"Sec-Fetch-Site\", \"same-origin\"], [\"Sec-Fetch-Mode\", \"no-cors\"], [\"Sec-Fetch-Dest\", \"script\"]];\n case \"media\":\n return [[\"Sec-Fetch-Site\", \"same-origin\"], [\"Sec-Fetch-Mode\", \"no-cors\"], [\"Sec-Fetch-Dest\", \"video\"]];\n case \"eventsource\":\n return [[\"Sec-Fetch-Site\", \"same-origin\"], [\"Sec-Fetch-Mode\", \"cors\"], [\"Sec-Fetch-Dest\", \"empty\"]];\n case \"iframe\":\n return [[\"Sec-Fetch-Site\", \"cross-site\"], [\"Sec-Fetch-Mode\", \"navigate\"], [\"Sec-Fetch-Dest\", \"iframe\"]];\n }\n}\n\nfunction acceptFor(kind: RequestKind, documentAccept: string): string {\n switch (kind) {\n case \"xhr\":\n case \"cors\":\n return \"*/*\";\n case \"eventsource\":\n return \"text/event-stream\";\n case \"subresource\":\n return \"image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8\";\n case \"stylesheet\":\n return \"text/css,*/*;q=0.1\";\n case \"script\":\n return \"*/*\";\n case \"media\":\n return \"*/*\";\n default:\n return documentAccept;\n }\n}\n\nconst isNavigation = (kind: RequestKind): boolean => kind.endsWith(\"navigate\") || kind === \"form-post\";\n\n/** Headers a browser adds only in particular circumstances. Shared by every engine. */\nfunction conditional(options: ResolvedOptions): { early: Header[]; late: Header[] } {\n const early: Header[] = [];\n const late: Header[] = [];\n\n if (options.reload === true) early.push([\"Cache-Control\", \"max-age=0\"]);\n if (options.earlyData === true) early.push([\"Early-Data\", \"1\"]);\n if (options.revalidate?.etag !== undefined) early.push([\"If-None-Match\", options.revalidate.etag]);\n if (options.revalidate?.modifiedSince !== undefined) early.push([\"If-Modified-Since\", options.revalidate.modifiedSince]);\n if (options.range !== undefined) early.push([\"Range\", options.range]);\n\n if (options.dnt === true) late.push([\"DNT\", \"1\"]);\n if (options.gpc === true) late.push([\"Sec-GPC\", \"1\"]);\n if (options.saveData === true) late.push([\"Save-Data\", \"on\"]);\n if (options.networkHints) {\n late.push([\"RTT\", String(options.networkHints.rtt)], [\"Downlink\", String(options.networkHints.downlink)], [\"ECT\", options.networkHints.ect]);\n }\n if (options.layoutHints) {\n late.push([\"Viewport-Width\", String(options.layoutHints.viewportWidth)], [\"DPR\", String(options.layoutHints.dpr)]);\n }\n if (options.prefetch === true) late.push([\"Sec-Purpose\", \"prefetch\"], [\"Purpose\", \"prefetch\"]);\n\n return { early, late };\n}\n\nfunction body(options: ResolvedOptions): Header[] {\n if (options.contentType === undefined) return [];\n const headers: Header[] = [[\"Content-Type\", options.contentType]];\n if (options.contentLength !== undefined) headers.push([\"Content-Length\", String(options.contentLength)]);\n return headers;\n}\n\n// --- Chromium ----------------------------------------------------------------\n\ninterface ChromiumBrands {\n /** Low-entropy brand list, always sent on a secure context. */\n brands: string;\n /** Full-version list, sent only once the server has advertised `Accept-CH`. */\n fullVersions: string;\n platform: string;\n platformVersion: string;\n mobile: boolean;\n model?: string;\n arch?: string;\n bitness?: string;\n}\n\n/**\n * Chromium's order: connection management, then the Client Hints block, then the\n * identity, then content negotiation split either side of the Fetch Metadata group.\n */\nfunction chromiumBuild(brands: ChromiumBrands) {\n return (options: ResolvedOptions, self: EngineProfile): Header[] => {\n const nav = isNavigation(options.kind);\n const { early, late } = conditional(options);\n const hints: Header[] = [\n [\"sec-ch-ua\", brands.brands],\n [\"sec-ch-ua-mobile\", brands.mobile ? \"?1\" : \"?0\"],\n [\"sec-ch-ua-platform\", `\"${brands.platform}\"`],\n ];\n if (options.highEntropyHints === true) {\n hints.push(\n [\"sec-ch-ua-full-version-list\", brands.fullVersions],\n [\"sec-ch-ua-platform-version\", `\"${brands.platformVersion}\"`],\n [\"sec-ch-ua-arch\", `\"${brands.arch ?? (brands.mobile ? \"arm\" : \"x86\")}\"`],\n [\"sec-ch-ua-bitness\", `\"${brands.bitness ?? \"64\"}\"`],\n [\"sec-ch-ua-model\", `\"${brands.model ?? \"\"}\"`],\n );\n }\n if (options.prefers?.colorScheme !== undefined) hints.push([\"Sec-CH-Prefers-Color-Scheme\", options.prefers.colorScheme]);\n if (options.prefers?.reducedMotion !== undefined) hints.push([\"Sec-CH-Prefers-Reduced-Motion\", options.prefers.reducedMotion]);\n\n return [\n [\"Host\", options.host],\n [\"Connection\", \"keep-alive\"],\n ...early,\n ...hints,\n ...(nav ? ([[\"Upgrade-Insecure-Requests\", \"1\"]] as Header[]) : []),\n [\"User-Agent\", self.userAgent],\n ...(options.origin !== undefined ? ([[\"Origin\", options.origin]] as Header[]) : []),\n ...body(options),\n [\"Accept\", acceptFor(options.kind, self.acceptDocument)],\n ...fetchMetadata(options.kind),\n ...(options.storageAccess !== undefined ? ([[\"Sec-Fetch-Storage-Access\", options.storageAccess]] as Header[]) : []),\n ...(options.referer !== undefined ? ([[\"Referer\", options.referer]] as Header[]) : []),\n [\"Accept-Encoding\", self.acceptEncoding],\n [\"Accept-Language\", options.acceptLanguage],\n ...late,\n [\"Priority\", nav ? \"u=0, i\" : \"u=1, i\"],\n ...(options.cookie !== undefined ? ([[\"Cookie\", options.cookie]] as Header[]) : []),\n ];\n };\n}\n\n// --- Gecko -------------------------------------------------------------------\n\n/** Firefox leads with identity and content negotiation, and closes with Fetch Metadata. */\nfunction geckoBuild(options: ResolvedOptions, self: EngineProfile): Header[] {\n const nav = isNavigation(options.kind);\n const { early, late } = conditional(options);\n return [\n [\"Host\", options.host],\n [\"User-Agent\", self.userAgent],\n [\"Accept\", acceptFor(options.kind, self.acceptDocument)],\n [\"Accept-Language\", options.acceptLanguage],\n [\"Accept-Encoding\", self.acceptEncoding],\n ...(options.referer !== undefined ? ([[\"Referer\", options.referer]] as Header[]) : []),\n ...(options.origin !== undefined ? ([[\"Origin\", options.origin]] as Header[]) : []),\n ...body(options),\n ...late,\n [\"Connection\", \"keep-alive\"],\n ...(options.cookie !== undefined ? ([[\"Cookie\", options.cookie]] as Header[]) : []),\n ...early,\n ...(nav ? ([[\"Upgrade-Insecure-Requests\", \"1\"]] as Header[]) : []),\n ...reorderForGecko(fetchMetadata(options.kind)),\n [\"Priority\", nav ? \"u=0, i\" : \"u=4\"],\n [\"TE\", \"trailers\"],\n ];\n}\n\n/** Gecko emits Dest, Mode, Site, User — the reverse of Chromium's grouping. */\nfunction reorderForGecko(headers: Header[]): Header[] {\n const order = [\"Sec-Fetch-Dest\", \"Sec-Fetch-Mode\", \"Sec-Fetch-Site\", \"Sec-Fetch-User\"];\n return order.flatMap((name) => headers.filter(([header]) => header === name));\n}\n\n// --- WebKit ------------------------------------------------------------------\n\n/** Safari interleaves Fetch Metadata with content negotiation rather than grouping it. */\nfunction webkitBuild(options: ResolvedOptions, self: EngineProfile): Header[] {\n const metadata = new Map(fetchMetadata(options.kind));\n const { early, late } = conditional(options);\n return [\n [\"Host\", options.host],\n ...early,\n ...(metadata.has(\"Sec-Fetch-Dest\") ? ([[\"Sec-Fetch-Dest\", metadata.get(\"Sec-Fetch-Dest\")!]] as Header[]) : []),\n [\"User-Agent\", self.userAgent],\n ...(options.origin !== undefined ? ([[\"Origin\", options.origin]] as Header[]) : []),\n ...body(options),\n [\"Accept\", acceptFor(options.kind, self.acceptDocument)],\n ...(metadata.has(\"Sec-Fetch-Site\") ? ([[\"Sec-Fetch-Site\", metadata.get(\"Sec-Fetch-Site\")!]] as Header[]) : []),\n [\"Accept-Language\", options.acceptLanguage],\n ...(metadata.has(\"Sec-Fetch-Mode\") ? ([[\"Sec-Fetch-Mode\", metadata.get(\"Sec-Fetch-Mode\")!]] as Header[]) : []),\n [\"Accept-Encoding\", self.acceptEncoding],\n ...(metadata.has(\"Sec-Fetch-User\") ? ([[\"Sec-Fetch-User\", metadata.get(\"Sec-Fetch-User\")!]] as Header[]) : []),\n ...(options.referer !== undefined ? ([[\"Referer\", options.referer]] as Header[]) : []),\n ...late,\n [\"Connection\", \"keep-alive\"],\n ...(options.cookie !== undefined ? ([[\"Cookie\", options.cookie]] as Header[]) : []),\n ];\n}\n\n/** Older WebKit and embedded builds: no Fetch Metadata at all. */\nfunction legacyWebkitBuild(options: ResolvedOptions, self: EngineProfile): Header[] {\n const { early, late } = conditional(options);\n return [\n [\"Host\", options.host],\n ...early,\n [\"User-Agent\", self.userAgent],\n ...body(options),\n [\"Accept\", acceptFor(options.kind, self.acceptDocument)],\n [\"Accept-Language\", options.acceptLanguage],\n [\"Accept-Encoding\", self.acceptEncoding],\n ...(options.referer !== undefined ? ([[\"Referer\", options.referer]] as Header[]) : []),\n ...late,\n [\"Connection\", \"keep-alive\"],\n ...(options.cookie !== undefined ? ([[\"Cookie\", options.cookie]] as Header[]) : []),\n ];\n}\n\n// --- Accept values -----------------------------------------------------------\n\nconst CHROME_ACCEPT = \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\";\nconst FIREFOX_ACCEPT = \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8\";\nconst SAFARI_ACCEPT = \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\";\nconst LEGACY_ACCEPT = \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\";\n\nfunction chromium(version: string, fullVersion: string, platform: string, platformVersion: string, mobile: boolean, extra?: Partial<ChromiumBrands>): ChromiumBrands {\n return {\n brands: `\"Not(A:Brand\";v=\"99\", \"Google Chrome\";v=\"${version}\", \"Chromium\";v=\"${version}\"`,\n fullVersions: `\"Not(A:Brand\";v=\"99.0.0.0\", \"Google Chrome\";v=\"${fullVersion}\", \"Chromium\";v=\"${fullVersion}\"`,\n platform,\n platformVersion,\n mobile,\n ...extra,\n };\n}\n\n// --- The profiles ------------------------------------------------------------\n\nexport const PROFILES = {\n // ---- Chromium desktop ----\n chromeWindows: {\n userAgent: \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36\",\n acceptDocument: CHROME_ACCEPT,\n acceptEncoding: \"gzip, deflate, br, zstd\",\n acceptLanguage: \"en-US,en;q=0.9\",\n build: chromiumBuild(chromium(\"152\", \"152.0.7258.67\", \"Windows\", \"15.0.0\", false)),\n },\n chromeMac: {\n userAgent: \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36\",\n acceptDocument: CHROME_ACCEPT,\n acceptEncoding: \"gzip, deflate, br, zstd\",\n acceptLanguage: \"en-GB,en-US;q=0.9,en;q=0.8\",\n build: chromiumBuild(chromium(\"152\", \"152.0.7258.67\", \"macOS\", \"15.6.0\", false, { arch: \"arm\" })),\n },\n chromeLinux: {\n userAgent: \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36\",\n acceptDocument: CHROME_ACCEPT,\n acceptEncoding: \"gzip, deflate, br, zstd\",\n acceptLanguage: \"en-US,en;q=0.9\",\n build: chromiumBuild(chromium(\"152\", \"152.0.7258.67\", \"Linux\", \"6.11.0\", false)),\n },\n chromeChromeOs: {\n userAgent: \"Mozilla/5.0 (X11; CrOS x86_64 15886.69.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36\",\n acceptDocument: CHROME_ACCEPT,\n acceptEncoding: \"gzip, deflate, br, zstd\",\n acceptLanguage: \"en-US,en;q=0.9\",\n build: chromiumBuild(chromium(\"151\", \"151.0.7204.183\", \"Chrome OS\", \"15886.69.0\", false)),\n },\n edgeWindows: {\n userAgent: \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36 Edg/150.0.0.0\",\n acceptDocument: CHROME_ACCEPT,\n acceptEncoding: \"gzip, deflate, br, zstd\",\n acceptLanguage: \"en-US,en;q=0.9\",\n build: chromiumBuild({\n brands: '\"Microsoft Edge\";v=\"150\", \"Not(A:Brand\";v=\"24\", \"Chromium\";v=\"150\"',\n fullVersions: '\"Microsoft Edge\";v=\"150.0.3296.62\", \"Not(A:Brand\";v=\"24.0.0.0\", \"Chromium\";v=\"150.0.7061.181\"',\n platform: \"Windows\",\n platformVersion: \"15.0.0\",\n mobile: false,\n }),\n },\n edgeMac: {\n userAgent: \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36 Edg/150.0.0.0\",\n acceptDocument: CHROME_ACCEPT,\n acceptEncoding: \"gzip, deflate, br, zstd\",\n acceptLanguage: \"en-US,en;q=0.9\",\n build: chromiumBuild({\n brands: '\"Microsoft Edge\";v=\"150\", \"Not(A:Brand\";v=\"24\", \"Chromium\";v=\"150\"',\n fullVersions: '\"Microsoft Edge\";v=\"150.0.3296.62\", \"Not(A:Brand\";v=\"24.0.0.0\", \"Chromium\";v=\"150.0.7061.181\"',\n platform: \"macOS\",\n platformVersion: \"15.6.0\",\n mobile: false,\n }),\n },\n operaWindows: {\n userAgent: \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36 OPR/135.0.0.0\",\n acceptDocument: CHROME_ACCEPT,\n acceptEncoding: \"gzip, deflate, br, zstd\",\n acceptLanguage: \"en-US,en;q=0.9\",\n build: chromiumBuild({\n brands: '\"Chromium\";v=\"149\", \"Not(A:Brand\";v=\"24\", \"Opera\";v=\"135\"',\n fullVersions: '\"Chromium\";v=\"149.0.7003.108\", \"Not(A:Brand\";v=\"24.0.0.0\", \"Opera\";v=\"135.0.6312.44\"',\n platform: \"Windows\",\n platformVersion: \"15.0.0\",\n mobile: false,\n }),\n },\n vivaldiWindows: {\n userAgent: \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Vivaldi/7.6.3797.48\",\n acceptDocument: CHROME_ACCEPT,\n acceptEncoding: \"gzip, deflate, br, zstd\",\n acceptLanguage: \"en-US,en;q=0.9\",\n build: chromiumBuild({\n brands: '\"Chromium\";v=\"148\", \"Not(A:Brand\";v=\"24\", \"Vivaldi\";v=\"7.6\"',\n fullVersions: '\"Chromium\";v=\"148.0.6873.120\", \"Not(A:Brand\";v=\"24.0.0.0\", \"Vivaldi\";v=\"7.6.3797.48\"',\n platform: \"Windows\",\n platformVersion: \"15.0.0\",\n mobile: false,\n }),\n },\n /** Brave presents an unmodified Chrome identity by design, and adds Sec-GPC. */\n braveWindows: {\n userAgent: \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36\",\n acceptDocument: CHROME_ACCEPT,\n acceptEncoding: \"gzip, deflate, br, zstd\",\n acceptLanguage: \"en-US,en;q=0.9\",\n build: chromiumBuild(chromium(\"152\", \"152.0.0.0\", \"Windows\", \"15.0.0\", false)),\n },\n yandexWindows: {\n userAgent: \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 YaBrowser/25.8.0.0 Safari/537.36\",\n acceptDocument: CHROME_ACCEPT,\n acceptEncoding: \"gzip, deflate, br, zstd\",\n acceptLanguage: \"ru,en;q=0.9\",\n build: chromiumBuild({\n brands: '\"Chromium\";v=\"146\", \"YaBrowser\";v=\"25.8\", \"Not(A:Brand\";v=\"24\", \"Yowser\";v=\"2.5\"',\n fullVersions: '\"Chromium\";v=\"146.0.6664.111\", \"YaBrowser\";v=\"25.8.0.1234\", \"Not(A:Brand\";v=\"24.0.0.0\"',\n platform: \"Windows\",\n platformVersion: \"15.0.0\",\n mobile: false,\n }),\n },\n\n // ---- Gecko ----\n firefoxWindows: {\n userAgent: \"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:148.0) Gecko/20100101 Firefox/148.0\",\n acceptDocument: FIREFOX_ACCEPT,\n acceptEncoding: \"gzip, deflate, br, zstd\",\n acceptLanguage: \"en-US,en;q=0.5\",\n build: geckoBuild,\n },\n firefoxMac: {\n userAgent: \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:148.0) Gecko/20100101 Firefox/148.0\",\n acceptDocument: FIREFOX_ACCEPT,\n acceptEncoding: \"gzip, deflate, br, zstd\",\n acceptLanguage: \"en-GB,en;q=0.5\",\n build: geckoBuild,\n },\n firefoxLinux: {\n userAgent: \"Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0\",\n acceptDocument: FIREFOX_ACCEPT,\n acceptEncoding: \"gzip, deflate, br, zstd\",\n acceptLanguage: \"de-DE,de;q=0.8,en-US;q=0.5,en;q=0.3\",\n build: geckoBuild,\n },\n firefoxEsr: {\n userAgent: \"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0\",\n acceptDocument: FIREFOX_ACCEPT,\n acceptEncoding: \"gzip, deflate, br, zstd\",\n acceptLanguage: \"en-US,en;q=0.5\",\n build: geckoBuild,\n },\n firefoxAndroid: {\n userAgent: \"Mozilla/5.0 (Android 15; Mobile; rv:148.0) Gecko/148.0 Firefox/148.0\",\n acceptDocument: FIREFOX_ACCEPT,\n acceptEncoding: \"gzip, deflate, br, zstd\",\n acceptLanguage: \"en-GB,en;q=0.5\",\n build: geckoBuild,\n },\n\n // ---- WebKit ----\n safariMac: {\n userAgent: \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.7 Safari/605.1.15\",\n acceptDocument: SAFARI_ACCEPT,\n acceptEncoding: \"gzip, deflate, br\",\n acceptLanguage: \"en-GB,en;q=0.9\",\n build: webkitBuild,\n },\n safariIos: {\n userAgent: \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.7 Mobile/15E148 Safari/604.1\",\n acceptDocument: SAFARI_ACCEPT,\n acceptEncoding: \"gzip, deflate, br\",\n acceptLanguage: \"en-US,en;q=0.9\",\n build: webkitBuild,\n },\n safariIpad: {\n userAgent: \"Mozilla/5.0 (iPad; CPU OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.7 Mobile/15E148 Safari/604.1\",\n acceptDocument: SAFARI_ACCEPT,\n acceptEncoding: \"gzip, deflate, br\",\n acceptLanguage: \"en-GB,en;q=0.9\",\n build: webkitBuild,\n },\n /** Chrome on iOS is WebKit underneath — the engine, and so the header order, is Safari's. */\n chromeIos: {\n userAgent: \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/152.0.7258.60 Mobile/15E148 Safari/604.1\",\n acceptDocument: SAFARI_ACCEPT,\n acceptEncoding: \"gzip, deflate, br\",\n acceptLanguage: \"en-US,en;q=0.9\",\n build: webkitBuild,\n },\n firefoxIos: {\n userAgent: \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/140.0 Mobile/15E148 Safari/605.1.15\",\n acceptDocument: SAFARI_ACCEPT,\n acceptEncoding: \"gzip, deflate, br\",\n acceptLanguage: \"en-US,en;q=0.9\",\n build: webkitBuild,\n },\n edgeIos: {\n userAgent: \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 EdgiOS/150.0.3296.60 Mobile/15E148 Safari/605.1.15\",\n acceptDocument: SAFARI_ACCEPT,\n acceptEncoding: \"gzip, deflate, br\",\n acceptLanguage: \"en-US,en;q=0.9\",\n build: webkitBuild,\n },\n duckduckgoIos: {\n userAgent: \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.7 Mobile/15E148 Safari/604.1 DuckDuckGo/7\",\n acceptDocument: SAFARI_ACCEPT,\n acceptEncoding: \"gzip, deflate, br\",\n acceptLanguage: \"en-US,en;q=0.9\",\n build: webkitBuild,\n },\n\n // ---- Chromium mobile ----\n chromeAndroid: {\n userAgent: \"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Mobile Safari/537.36\",\n acceptDocument: CHROME_ACCEPT,\n acceptEncoding: \"gzip, deflate, br, zstd\",\n acceptLanguage: \"en-GB,en;q=0.9\",\n build: chromiumBuild(chromium(\"150\", \"150.0.7061.181\", \"Android\", \"15.0.0\", true, { model: \"Pixel 9\" })),\n },\n chromeAndroidTablet: {\n userAgent: \"Mozilla/5.0 (Linux; Android 15; SM-X910) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36\",\n acceptDocument: CHROME_ACCEPT,\n acceptEncoding: \"gzip, deflate, br, zstd\",\n acceptLanguage: \"en-US,en;q=0.9\",\n build: chromiumBuild(chromium(\"150\", \"150.0.7061.181\", \"Android\", \"15.0.0\", false, { model: \"SM-X910\" })),\n },\n samsungInternet: {\n userAgent: \"Mozilla/5.0 (Linux; Android 15; SM-S938B) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/29.0 Chrome/147.0.0.0 Mobile Safari/537.36\",\n acceptDocument: CHROME_ACCEPT,\n acceptEncoding: \"gzip, deflate, br\",\n acceptLanguage: \"ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7\",\n build: chromiumBuild({\n brands: '\"Chromium\";v=\"147\", \"Not(A:Brand\";v=\"24\", \"Samsung Internet\";v=\"29.0\"',\n fullVersions: '\"Chromium\";v=\"147.0.6929.94\", \"Not(A:Brand\";v=\"24.0.0.0\", \"Samsung Internet\";v=\"29.0.0.0\"',\n platform: \"Android\",\n platformVersion: \"15.0.0\",\n mobile: true,\n model: \"SM-S938B\",\n }),\n },\n operaAndroid: {\n userAgent: \"Mozilla/5.0 (Linux; Android 14; CPH2451) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Mobile Safari/537.36 OPR/91.0.0.0\",\n acceptDocument: CHROME_ACCEPT,\n acceptEncoding: \"gzip, deflate, br\",\n acceptLanguage: \"id-ID,id;q=0.9,en-US;q=0.8\",\n build: chromiumBuild({\n brands: '\"Chromium\";v=\"146\", \"Not(A:Brand\";v=\"24\", \"Opera\";v=\"91\"',\n fullVersions: '\"Chromium\";v=\"146.0.6664.111\", \"Not(A:Brand\";v=\"24.0.0.0\", \"Opera\";v=\"91.0.4516.22\"',\n platform: \"Android\",\n platformVersion: \"14.0.0\",\n mobile: true,\n model: \"CPH2451\",\n }),\n },\n edgeAndroid: {\n userAgent: \"Mozilla/5.0 (Linux; Android 15; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Mobile Safari/537.36 EdgA/150.0.3296.60\",\n acceptDocument: CHROME_ACCEPT,\n acceptEncoding: \"gzip, deflate, br, zstd\",\n acceptLanguage: \"en-US,en;q=0.9\",\n build: chromiumBuild({\n brands: '\"Microsoft Edge\";v=\"150\", \"Not(A:Brand\";v=\"24\", \"Chromium\";v=\"150\"',\n fullVersions: '\"Microsoft Edge\";v=\"150.0.3296.60\", \"Not(A:Brand\";v=\"24.0.0.0\", \"Chromium\";v=\"150.0.7061.181\"',\n platform: \"Android\",\n platformVersion: \"15.0.0\",\n mobile: true,\n model: \"Pixel 9\",\n }),\n },\n /** UC Browser — very large user base across South and Southeast Asia. */\n ucBrowser: {\n userAgent: \"Mozilla/5.0 (Linux; U; Android 13; en-IN; RMX3771 Build/TP1A.220905.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.58 UCBrowser/13.7.5.1329 Mobile Safari/537.36\",\n acceptDocument: LEGACY_ACCEPT,\n acceptEncoding: \"gzip, deflate\",\n acceptLanguage: \"en-IN,en-US;q=0.9,en;q=0.8,hi;q=0.7\",\n build: legacyWebkitBuild,\n },\n miBrowser: {\n userAgent: \"Mozilla/5.0 (Linux; U; Android 14; en-in; 23049PCD8I Build/UKQ1.230917.001) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Chrome/125.0.6422.165 Mobile Safari/533.1 XiaoMi/MiuiBrowser/19.4.220521\",\n acceptDocument: LEGACY_ACCEPT,\n acceptEncoding: \"gzip, deflate\",\n acceptLanguage: \"en-IN,en;q=0.9\",\n build: legacyWebkitBuild,\n },\n huaweiBrowser: {\n userAgent: \"Mozilla/5.0 (Linux; Android 12; ELS-NX9; HMSCore 6.14.0.302) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.186 HuaweiBrowser/15.0.5.310 Mobile Safari/537.36\",\n acceptDocument: CHROME_ACCEPT,\n acceptEncoding: \"gzip, deflate, br\",\n acceptLanguage: \"zh-CN,zh;q=0.9,en;q=0.8\",\n build: legacyWebkitBuild,\n },\n qqBrowser: {\n userAgent: \"Mozilla/5.0 (Linux; U; Android 14; zh-cn; 2211133C Build/UKQ1.230804.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/122.0.6261.119 MQQBrowser/15.7 Mobile Safari/537.36\",\n acceptDocument: LEGACY_ACCEPT,\n acceptEncoding: \"gzip, deflate\",\n acceptLanguage: \"zh-CN,zh;q=0.9\",\n build: legacyWebkitBuild,\n },\n\n // ---- Consoles, televisions and embedded ----\n silkKindle: {\n userAgent: \"Mozilla/5.0 (Linux; Android 11; KFRAWI) AppleWebKit/537.36 (KHTML, like Gecko) Silk/128.1.2 like Chrome/128.0.6613.146 Safari/537.36\",\n acceptDocument: CHROME_ACCEPT,\n acceptEncoding: \"gzip, deflate, br\",\n acceptLanguage: \"en-GB,en;q=0.9\",\n build: legacyWebkitBuild,\n },\n tizenTv: {\n userAgent: \"Mozilla/5.0 (SMART-TV; LINUX; Tizen 8.0) AppleWebKit/537.36 (KHTML, like Gecko) 108.0.5359.1/8.0 TV Safari/537.36\",\n acceptDocument: LEGACY_ACCEPT,\n acceptEncoding: \"gzip, deflate\",\n acceptLanguage: \"en-US\",\n build: legacyWebkitBuild,\n },\n webOsTv: {\n userAgent: \"Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.215 Safari/537.36 WebAppManager\",\n acceptDocument: LEGACY_ACCEPT,\n acceptEncoding: \"gzip, deflate\",\n acceptLanguage: \"en-GB\",\n build: legacyWebkitBuild,\n },\n playstation: {\n userAgent: \"Mozilla/5.0 (PlayStation; PlayStation 5/9.60) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15\",\n acceptDocument: SAFARI_ACCEPT,\n acceptEncoding: \"gzip, deflate, br\",\n acceptLanguage: \"en-GB\",\n build: legacyWebkitBuild,\n },\n nintendoSwitch: {\n userAgent: \"Mozilla/5.0 (Nintendo Switch; WifiWebAuthApplet) AppleWebKit/609.4 (KHTML, like Gecko) NF/6.0.2.23.4 NintendoBrowser/5.1.0.23519\",\n acceptDocument: LEGACY_ACCEPT,\n acceptEncoding: \"gzip, deflate\",\n acceptLanguage: \"en-GB\",\n build: legacyWebkitBuild,\n },\n} as const satisfies Record<string, EngineProfile>;\n\nexport type ProfileName = keyof typeof PROFILES;\nexport const PROFILE_NAMES = Object.keys(PROFILES) as ProfileName[];\n\n/** Builds a request from a named profile. The normal way to write a human case. */\nexport function browser(name: ProfileName, options: ProfileOptions = {}): CaseRequest {\n const profile = PROFILES[name] as EngineProfile;\n const resolved: ResolvedOptions = {\n ...options,\n host: options.host ?? HOST,\n kind: options.kind ?? \"navigate\",\n acceptLanguage: options.acceptLanguage ?? profile.acceptLanguage,\n };\n return {\n headers: profile.build(resolved, profile),\n protocol: \"https\",\n httpVersion: \"1.1\",\n ...(options.kind === \"form-post\" ? { method: \"POST\" } : {}),\n };\n}\n\n/** The raw User-Agent of a profile, for cases that alter it deliberately. */\nexport function userAgentOf(name: ProfileName): string {\n return (PROFILES[name] as EngineProfile).userAgent;\n}\n\n/**\n * A minimal bot-shaped request: a User-Agent and whatever else you name.\n *\n * Most automation really does send this little. Where a specific client is known to\n * send more, the case says so explicitly rather than using this helper.\n */\nexport function plain(userAgent: string, extra: readonly Header[] = [], host = HOST): CaseRequest {\n // `extra` *replaces* a default of the same name rather than following it. A client\n // that sent `Accept: */*` and then a second, different `Accept` does not exist, and\n // building one here made two human fixtures look hand-assembled the moment\n // `header-integrity` learned to read repeated fields.\n const overridden = new Set(extra.map(([name]) => name.toLowerCase()));\n const defaults: Header[] = ([[\"Host\", host], [\"User-Agent\", userAgent], [\"Accept\", \"*/*\"]] as Header[]).filter(([name]) => !overridden.has(name.toLowerCase()));\n return {\n headers: [...defaults, ...extra],\n protocol: \"https\",\n httpVersion: \"1.1\",\n };\n}\n\n/**\n * A well-behaved crawler's request: identity, a contact address, and the negotiation\n * headers a fetcher that intends to parse HTML actually sends.\n */\nexport function crawler(userAgent: string, options: { from?: string; accept?: string; encoding?: string; host?: string; extra?: readonly Header[] } = {}): CaseRequest {\n return {\n headers: [\n [\"Host\", options.host ?? HOST],\n [\"User-Agent\", userAgent],\n [\"Accept\", options.accept ?? \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"],\n [\"Accept-Encoding\", options.encoding ?? \"gzip, deflate, br\"],\n ...(options.from !== undefined ? ([[\"From\", options.from]] as Header[]) : []),\n [\"Connection\", \"keep-alive\"],\n ...(options.extra ?? []),\n ],\n protocol: \"https\",\n httpVersion: \"1.1\",\n };\n}\n","import type { ActionName } from \"../policy/types.js\";\nimport type { BotClass, Verdict } from \"../types.js\";\n\n/**\n * The corpus schema.\n *\n * This is a body of *realistic web traffic* — real User-Agent strings, real header\n * sets in the order real clients send them, real behavioural shapes — paired with\n * what the library ought to conclude about each. It exists to answer one question\n * that no unit test can:\n *\n * > If I point this configuration at the actual internet, who gets hurt?\n *\n * The corpus is deliberately separate from the configuration it tests. Point the\n * runner at your own `BotHandler` and it tells you how *your* policy behaves against\n * every shape of traffic in here — not how the defaults behave.\n *\n * ## Adding cases\n *\n * Add to the file matching the audience, or create a new file and register it in\n * `index.ts`. Every case needs `provenance`: where the shape came from. A fixture\n * somebody invented is worth much less than one copied from a log, and six months\n * later nobody can tell them apart unless it is written down.\n */\n\n/**\n * Who is behind the request — and, more to the point, what it costs to be wrong.\n *\n * This is the axis that matters, because the corpus's central assertion is expressed\n * in terms of it: **no case marked `human` may ever be denied service.** That check\n * runs against every case in the corpus regardless of what its own expectations say,\n * so a new human case protects you the moment it is added.\n */\nexport type Audience =\n /** A person. Being wrong here means turning away a customer. */\n | \"human\"\n /** Automation almost every site wants: search crawlers, link unfurlers, uptime monitors. */\n | \"benign-bot\"\n /** Honest automation whose treatment is a business decision, not a security one. */\n | \"declared-bot\"\n /** Automation most sites would rather not serve: aggressive SEO, bulk extraction. */\n | \"unwanted-bot\"\n /** Scanners, forged identities, credential attacks. */\n | \"hostile\"\n /** Something in the middle of the path: a proxy, a CDN, a corporate gateway. */\n | \"infrastructure\";\n\n/** One HTTP request, as a client would actually send it. */\nexport interface CaseRequest {\n method?: string;\n /** Path plus query, as it appears on the request line. */\n path?: string;\n /**\n * Headers **in the order the client sends them**.\n *\n * A tuple list rather than an object, because order is itself a fingerprint and an\n * object literal's key order is too easy to disturb by accident. `headers.ts` builds\n * these for the real browsers.\n */\n headers: ReadonlyArray<readonly [name: string, value: string]>;\n ip?: string;\n protocol?: \"http\" | \"https\";\n httpVersion?: string;\n tlsFingerprint?: string;\n /** Milliseconds after the case's start time. Drives the behavioural detectors. */\n atMs?: number;\n /** The source could not supply the full header set. See `RequestFacts.partialHeaders`. */\n partialHeaders?: boolean;\n}\n\nexport type OneOrMany<T> = T | readonly T[];\n\n/** What the library should conclude. Every field is optional; absent means \"don't care\". */\nexport interface Expectation {\n verdict?: OneOrMany<Verdict>;\n botClass?: OneOrMany<BotClass>;\n /** Whether the conclusion must rest on proven evidence. */\n certain?: boolean;\n /** Established or claimed identity, e.g. `\"googlebot\"`. */\n identity?: string;\n minScore?: number;\n maxScore?: number;\n /** Detectors that must all have produced evidence. */\n detectors?: readonly string[];\n /** Detectors that must not have fired. The false-positive guard, stated per case. */\n notDetectors?: readonly string[];\n /** Actions that are acceptable. */\n action?: OneOrMany<ActionName>;\n /** Actions that must never be taken. Enforced in addition to the audience rule. */\n neverAction?: readonly ActionName[];\n /**\n * What the action layer actually did, which is not always what the policy decided.\n *\n * A decision to challenge an actor that already holds clearance is refused and the\n * request is served instead — the guard that stops a proven bot from looping on the\n * challenge forever. That distinction is invisible from `action` alone.\n */\n outcome?: \"continue\" | \"respond\" | \"drop\";\n}\n\nexport interface TrafficCase {\n /** Stable, unique, kebab-case. Appears in every report; treat it as an identifier. */\n id: string;\n title: string;\n audience: Audience;\n /** Finer grouping within an audience, e.g. `\"desktop-browser\"`, `\"ai-crawler\"`. */\n category: string;\n /**\n * Where this shape came from: a published User-Agent list, a vendor's documentation,\n * an observed log line, a specification. Required — a fixture nobody can trace is a\n * fixture nobody can update when the world moves.\n */\n provenance: string;\n /** Anything a reader needs in order to judge whether the expectation is right. */\n notes?: string;\n /** A single request, or an ordered sequence from one actor. */\n requests: readonly CaseRequest[];\n expect: Expectation;\n tags?: readonly string[];\n /**\n * Configuration this case depends on, as free-text capability names.\n *\n * Some traffic can only be classified by a detector that has been *told* something\n * — the trap detector cannot recognise a honeypot form field whose name it was\n * never given. Rather than quietly failing against a default configuration, such a\n * case declares what it needs; the runner skips it and says so when the handler\n * under test does not provide it. A skipped case is reported, never counted as a\n * pass, because \"we did not check\" and \"it worked\" must not look the same.\n */\n requires?: readonly string[];\n /**\n * Mint a clearance token for this actor before the case runs, and attach it to\n * every request.\n *\n * The token has to be signed by the handler under test, so the corpus cannot carry\n * a literal cookie — it declares the level it wants and the runner asks the handler\n * for one. `operator` is the only level the library treats as conclusive proof of a\n * person, because it is an assertion by your application rather than an inference\n * from the request.\n */\n clearance?: \"pow\" | \"interaction\" | \"operator\";\n /**\n * A person whose *client software* declares itself automated, and why.\n *\n * The never-deny guarantee attached to `human` cases is a promise about guesses:\n * nobody is refused on the strength of an inference. It is not a promise that the\n * library can see through a client that announces itself as a bot. A podcast\n * application uses one User-Agent both to fetch feeds and to open the links a\n * listener taps; when the listener taps one, the library reads the declaration the\n * software made and is correct to.\n *\n * Setting this exempts the case from the audience rule and, crucially, lists it in\n * the scorecard under its own heading. These are the people the design knowingly\n * cannot protect, and the corpus makes them countable rather than invisible.\n */\n selfDeclared?: string;\n /**\n * DNS the runner should present while this case runs.\n *\n * Forward-confirmed reverse DNS is the only mechanism that can produce a\n * `verified-bot` or prove an `impersonator`, so a corpus that cannot control DNS\n * cannot test the two most consequential verdicts the library reaches. `reverse`\n * maps address to PTR names; `forward` maps name to addresses. A name absent from\n * either map resolves to NXDOMAIN, which is a *definitive* negative answer — the\n * runner never presents a timeout unless a case asks for one, because \"no answer\"\n * and \"the wrong answer\" must lead to different verdicts.\n */\n dns?: {\n reverse?: Readonly<Record<string, readonly string[]>>;\n forward?: Readonly<Record<string, readonly string[]>>;\n /** Make every lookup time out, to exercise the indeterminate path. */\n unavailable?: boolean;\n };\n}\n\n/** Terminal actions. A human case reaching any of these is a corpus failure. */\nexport const DENYING_ACTIONS: readonly ActionName[] = [\"block\", \"drop\", \"redirect\"];\n\n/**\n * Declares a case involving a person.\n *\n * Adds the never-deny guarantee automatically, so it cannot be forgotten, and defaults\n * the verdict expectation to \"not classified as a bot\". Override `expect` for the\n * awkward cases — and there are several, because some real people do look automated.\n */\nexport function human(input: Omit<TrafficCase, \"audience\"> & { audience?: never }): TrafficCase {\n return {\n ...input,\n audience: \"human\",\n expect: {\n neverAction: DENYING_ACTIONS,\n ...input.expect,\n },\n };\n}\n\n/** Declares a case involving automation. No implicit guarantees; say what you mean. */\nexport function bot(input: TrafficCase): TrafficCase {\n return input;\n}\n\n/** Repeats a request `count` times at a fixed interval. For rate and cadence shapes. */\nexport function repeat(template: CaseRequest, count: number, everyMs: number, pathAt?: (index: number) => string): CaseRequest[] {\n return Array.from({ length: count }, (_, index) => ({\n ...template,\n ...(pathAt ? { path: pathAt(index) } : {}),\n atMs: index * everyMs,\n }));\n}\n\n/**\n * Repeats a request with *human* pacing — irregular gaps drawn from a fixed sequence.\n *\n * Deterministic on purpose. A corpus that uses a random number generator produces a\n * different verdict on Tuesday than it did on Monday, and then nobody trusts it.\n */\nconst HUMAN_GAPS_MS = [1_400, 8_200, 3_100, 21_000, 2_600, 47_000, 5_900, 1_100, 12_400, 3_800, 64_000, 2_200, 9_700, 1_800];\n\nexport function humanPaced(template: CaseRequest, paths: readonly string[]): CaseRequest[] {\n let at = 0;\n return paths.map((path, index) => {\n if (index > 0) at += HUMAN_GAPS_MS[(index - 1) % HUMAN_GAPS_MS.length]!;\n return { ...template, path, atMs: at };\n });\n}\n","/**\n * Published IP ranges the corpus assumes.\n *\n * Several crawlers publish an exhaustive address list instead of setting PTR records,\n * and the library can only confirm or refute those claims when it has been handed the\n * list. These are the fictional stand-ins the corpus uses, drawn entirely from the\n * documentation ranges reserved by RFC 5737 so that nothing here can ever collide\n * with a real network.\n *\n * The convention, which the cases rely on:\n *\n * - **inside** the range → the claim is confirmed → `verified-bot`\n * - **outside** the range → the claim is refuted → `impersonator`\n *\n * Cases pick their address to land on the side they mean to test.\n */\nexport const CORPUS_CRAWLER_RANGES: Readonly<Record<string, readonly string[]>> = {\n // Amazonbot is deliberately absent. Something has to exercise the third outcome —\n // a claim that can be neither confirmed nor refuted because no list was supplied —\n // and that outcome must never look like an accusation.\n gptbot: [\"198.51.100.0/25\"],\n \"oai-searchbot\": [\"198.51.100.0/25\"],\n \"chatgpt-user\": [\"198.51.100.0/25\"],\n claudebot: [\"198.51.100.128/26\"],\n perplexitybot: [\"198.51.100.192/27\"],\n \"meta-ai\": [\"198.51.100.240/29\"],\n \"facebook-external\": [\"198.51.100.240/29\"],\n duckduckbot: [\"198.51.100.248/30\"],\n uptimerobot: [\"198.51.100.252/31\"],\n pingdom: [\"198.51.100.254/32\"],\n};\n\n/** An address inside a crawler's published range. */\nexport const IN_RANGE: Readonly<Record<string, string>> = {\n gptbot: \"198.51.100.10\",\n \"oai-searchbot\": \"198.51.100.11\",\n \"chatgpt-user\": \"198.51.100.12\",\n claudebot: \"198.51.100.130\",\n perplexitybot: \"198.51.100.194\",\n \"meta-ai\": \"198.51.100.241\",\n \"facebook-external\": \"198.51.100.242\",\n duckduckbot: \"198.51.100.249\",\n uptimerobot: \"198.51.100.252\",\n pingdom: \"198.51.100.254\",\n};\n\n/** Outside every published range above — the address a forgery would come from. */\nexport const OUT_OF_RANGE = \"192.0.2.66\";\n\n/**\n * Real Googlebot address space, used only as a *shape* for the FCrDNS cases.\n *\n * No lookup ever leaves the process: the runner answers from each case's own `dns`\n * map. The address is realistic so that a reader recognises what is being modelled.\n */\nexport const GOOGLEBOT_IP = \"66.249.66.1\";\nexport const GOOGLEBOT_PTR = \"crawl-66-249-66-1.googlebot.com\";\nexport const BINGBOT_IP = \"40.77.167.1\";\nexport const BINGBOT_PTR = \"msnbot-40-77-167-1.search.msn.com\";\n","import { plain } from \"./headers.js\";\nimport { bot } from \"./schema.js\";\nimport { IN_RANGE, OUT_OF_RANGE } from \"./ranges.js\";\nimport type { TrafficCase } from \"./schema.js\";\n\n/**\n * The AI fleet.\n *\n * Every vendor runs several bots with different jobs, and the distinction is the\n * whole point: a **training** crawler collects content for a future model, a\n * **search** crawler indexes pages so an assistant can cite them, and a **user** agent\n * fetches one page because somebody just asked about it. Those are three different\n * bargains. A publisher may well want to decline training, keep search — being cited\n * is traffic — and think carefully about user agents, which are arguably a person\n * with a tool rather than a bot at all.\n *\n * The library's job is to name them precisely enough that the policy can make that\n * distinction. Whether to serve them is a business decision, and the corpus takes no\n * position on it — every case here expects correct *identification*, not a particular\n * action.\n *\n * By 2026 this is the fastest-growing segment of automated traffic: training crawlers\n * fell from 90% to 74% of AI-driven requests over 2025 while scrapers rose to 24%,\n * and a new agentic category appeared. Every one of these declares itself honestly,\n * which is the only reason a rule matching them works at all.\n */\n\nfunction ai(id: string, title: string, userAgent: string, identity: string, provenance: string, notes?: string): TrafficCase {\n return bot({\n id,\n title,\n audience: \"declared-bot\",\n category: \"ai-crawler\",\n provenance,\n ...(notes !== undefined ? { notes } : {}),\n requests: [plain(userAgent)],\n expect: { verdict: \"confirmed-bot\", certain: true, identity, detectors: [\"self-identified\"] },\n });\n}\n\nexport const AI_CRAWLER_CASES: TrafficCase[] = [\n // --- OpenAI's three ---------------------------------------------------------\n bot({\n id: \"gptbot-in-range\",\n requires: [\"crawler-ranges\"],\n title: \"GPTBot from OpenAI's published range\",\n audience: \"declared-bot\",\n category: \"ai-crawler\",\n provenance: \"OpenAI publishes its crawler ranges at platform.openai.com/docs/bots\",\n notes: \"Training crawler. Confirmable only because the ranges were supplied — with none configured this is an unverifiable claim, which is the next case.\",\n requests: [{ ...plain(\"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.2; +https://openai.com/gptbot\"), ip: IN_RANGE[\"gptbot\"]! }],\n expect: { verdict: \"verified-bot\", certain: true, identity: \"gptbot\", detectors: [\"crawler-verification\"] },\n tags: [\"verification\"],\n }),\n bot({\n id: \"amazonbot-no-ranges-configured\",\n title: \"Amazonbot when no ranges have been configured for it\",\n audience: \"declared-bot\",\n category: \"ai-crawler\",\n provenance: \"The library ships no address data; a claim it cannot check is left unchecked\",\n notes:\n \"The third outcome, and the one most easily got wrong. Unverifiable is not the same as false: with no published list to compare against, the claim stands on its own honesty and the crawler is neither confirmed nor accused.\",\n requests: [{ ...plain(\"Mozilla/5.0 (Linux; Android 6.0.1;) AppleWebKit/537.36 (KHTML, like Gecko) Mobile Safari/537.36 (compatible; Amazonbot/0.1; +https://developer.amazon.com/support/amazonbot)\"), ip: \"203.0.113.77\" }],\n expect: { verdict: \"confirmed-bot\", botClass: \"declared-bot\", certain: true, identity: \"amazonbot\", notDetectors: [\"crawler-verification\"] },\n tags: [\"verification\"],\n }),\n bot({\n id: \"gptbot-forged\",\n requires: [\"crawler-ranges\"],\n title: \"A forged GPTBot from outside the published range\",\n audience: \"hostile\",\n category: \"ai-crawler\",\n provenance: \"Claiming a crawler identity is the cheapest way to ask for privileged treatment\",\n notes: \"Proven false, not merely suspected: the operator publishes the exhaustive list and this address is not on it.\",\n requests: [{ ...plain(\"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.2; +https://openai.com/gptbot\"), ip: OUT_OF_RANGE }],\n expect: { verdict: \"confirmed-bot\", botClass: \"impersonator\", certain: true, identity: \"gptbot\" },\n tags: [\"verification\", \"impersonation\"],\n }),\n ai(\"oai-searchbot\", \"OAI-SearchBot\", \"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; OAI-SearchBot/1.0; +https://openai.com/searchbot\", \"oai-searchbot\", \"Search indexing for ChatGPT citations — declining this removes you from answers\", \"Distinct from GPTBot. Blocking both when you meant to block training is a common and costly mistake.\"),\n ai(\"chatgpt-user\", \"ChatGPT-User\", \"Mozilla/5.0 (compatible; ChatGPT-User/1.0; +https://openai.com/bot)\", \"chatgpt-user\", \"Fetches a page because a user asked about it, in real time\", \"Arguably a person holding a tool rather than a crawler. Rate-limiting it is reasonable; blocking it denies a reader.\"),\n\n // --- Anthropic --------------------------------------------------------------\n bot({\n id: \"claudebot-in-range\",\n requires: [\"crawler-ranges\"],\n title: \"ClaudeBot from its published range\",\n audience: \"declared-bot\",\n category: \"ai-crawler\",\n provenance: \"Anthropic documents its crawlers and publishes ranges\",\n requests: [{ ...plain(\"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ClaudeBot/1.0; +claudebot@anthropic.com)\"), ip: IN_RANGE[\"claudebot\"]! }],\n expect: { verdict: \"verified-bot\", certain: true, identity: \"claudebot\" },\n tags: [\"verification\"],\n }),\n ai(\"claude-user\", \"Claude-User\", \"Mozilla/5.0 (compatible; Claude-User/1.0; +Claude-User@anthropic.com)\", \"claudebot\", \"Fetches a page on a user's behalf during a conversation\"),\n ai(\"claude-searchbot\", \"Claude-SearchBot\", \"Mozilla/5.0 (compatible; Claude-SearchBot/1.0; +Claude-SearchBot@anthropic.com)\", \"claudebot\", \"Indexes pages so Claude can cite them\"),\n\n // --- The rest ---------------------------------------------------------------\n bot({\n id: \"perplexitybot-in-range\",\n requires: [\"crawler-ranges\"],\n title: \"PerplexityBot from its published range\",\n audience: \"declared-bot\",\n category: \"ai-crawler\",\n provenance: \"Perplexity publishes ranges for its indexing crawler\",\n requests: [{ ...plain(\"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; PerplexityBot/1.0; +https://perplexity.ai/perplexitybot\"), ip: IN_RANGE[\"perplexitybot\"]! }],\n expect: { verdict: \"verified-bot\", certain: true, identity: \"perplexitybot\" },\n tags: [\"verification\"],\n }),\n ai(\"perplexity-user\", \"Perplexity-User\", \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Perplexity-User/1.0; +https://perplexity.ai/perplexity-user\", \"perplexitybot\", \"Real-time fetch on a user's request\"),\n ai(\"google-extended\", \"Google-Extended\", \"Mozilla/5.0 (compatible; Google-Extended/1.0)\", \"google-extended\", \"A robots.txt token controlling Gemini training, not a separate crawler — declining it does not affect Search ranking\", \"Worth understanding: this is a *policy token* Google honours, not a distinct fetcher. Confusing it with Googlebot loses your search traffic for nothing.\"),\n ai(\"applebot-extended\", \"Applebot-Extended\", \"Mozilla/5.0 (compatible; Applebot-Extended/0.1; +http://www.apple.com/go/applebot)\", \"applebot-extended\", \"Apple's training-opt-out token, paired with Applebot for search\"),\n ai(\"ccbot\", \"CCBot (Common Crawl)\", \"CCBot/2.0 (https://commoncrawl.org/faq/)\", \"ccbot\", \"Feeds the Common Crawl corpus, which most open models were trained on\"),\n ai(\"meta-externalagent\", \"Meta-ExternalAgent\", \"meta-externalagent/1.1 (+https://developers.facebook.com/docs/sharing/webmasters/crawler)\", \"meta-ai\", \"Meta's AI training crawler, separate from facebookexternalhit\"),\n ai(\"meta-externalfetcher\", \"Meta-ExternalFetcher\", \"meta-externalfetcher/1.1 (+https://developers.facebook.com/docs/sharing/webmasters/crawler)\", \"meta-ai\", \"Fetches on a user's behalf inside Meta AI\"),\n ai(\"cohere-ai\", \"Cohere\", \"cohere-training-data-crawler/1.0 (+https://cohere.com/data-crawler)\", \"cohere-ai\", \"Training data collection\"),\n ai(\"diffbot\", \"Diffbot\", \"Mozilla/5.0 (compatible; Diffbot/0.1; +http://www.diffbot.com)\", \"diffbot\", \"Structured extraction sold as a product — a crawler with mixed robots.txt compliance\"),\n ai(\"ai2bot\", \"AI2Bot\", \"Mozilla/5.0 (compatible) AI2Bot (+https://www.allenai.org/crawler)\", \"ai2bot\", \"The Allen Institute's research crawler\"),\n ai(\"youbot\", \"YouBot\", \"Mozilla/5.0 (compatible; YouBot (+http://www.you.com))\", \"youbot\", \"You.com's search and answer crawler\"),\n ai(\"timpibot\", \"Timpibot\", \"Mozilla/5.0 (compatible; Timpibot/0.1; +http://www.timpi.io)\", \"timpibot\", \"Decentralised index crawler\"),\n\n bot({\n id: \"bytespider\",\n title: \"Bytespider\",\n audience: \"unwanted-bot\",\n category: \"ai-crawler\",\n provenance: \"ByteDance's crawler. Widely reported for aggressive rates and inconsistent robots.txt compliance, and it publishes no vendor documentation page at all.\",\n notes:\n \"Classified `unwanted-bot` rather than `declared-bot` on its behaviour, not its honesty — it does identify itself. The distinction is a policy judgement the corpus records rather than a technical one.\",\n requests: [plain(\"Mozilla/5.0 (Linux; Android 5.0) AppleWebKit/537.36 (KHTML, like Gecko) Mobile Safari/537.36 (compatible; Bytespider; spider-feedback@bytedance.com)\")],\n expect: { verdict: \"confirmed-bot\", certain: true, identity: \"bytespider\" },\n }),\n\n bot({\n id: \"agentic-browser\",\n title: \"An agentic assistant driving a real browser on a user's behalf\",\n audience: \"declared-bot\",\n category: \"ai-agent\",\n provenance:\n \"The category that appeared during 2025 and grew fastest through 2026: an assistant operating a real browser to complete a task a person asked for. Some announce themselves; this one does.\",\n notes:\n \"The hardest case in the corpus, and not a technical problem. Every signal says automation, and the honest description is 'a person, using a tool, that happens to be a browser being driven'. The library reports what it sees; whether that should be served is a question about your business, not your logs.\",\n requests: [\n plain(\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36 Autonomous-Agent/1.0 (+https://example-agent.ai/bot)\",\n [[\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"], [\"Accept-Language\", \"en-US,en;q=0.9\"], [\"Accept-Encoding\", \"gzip, deflate, br\"]],\n ),\n ],\n expect: { verdict: \"confirmed-bot\", botClass: \"declared-bot\", certain: true },\n tags: [\"frontier\"],\n }),\n];\n","import { browser, plain, userAgentOf } from \"./headers.js\";\nimport { bot } from \"./schema.js\";\nimport { GOOGLEBOT_IP, OUT_OF_RANGE } from \"./ranges.js\";\nimport type { TrafficCase } from \"./schema.js\";\n\n/**\n * Traffic that is trying not to be caught.\n *\n * This is where the library's limits are honest rather than flattering. The cases run\n * from crude to genuinely hard, and the hard ones are expected to *not* be proven —\n * because they are not provable from a single request, and claiming otherwise is how\n * a detector starts blocking people.\n *\n * The progression is deliberate:\n *\n * 1. A scraper that copies a User-Agent and nothing else. Caught easily.\n * 2. One that copies the whole header set but not the order. Caught, weakly.\n * 3. One that copies the order too. Not caught from one request — only behaviour is\n * left, and behaviour cannot prove anything.\n * 4. One that also paces itself like a person. Not caught at all, and the corpus says\n * so rather than inventing a signal.\n */\n\nconst CHROME_UA = userAgentOf(\"chromeWindows\");\n\nexport const ADVERSARIAL_CASES: TrafficCase[] = [\n // ---------------------------------------------------------------------------\n // Forged identities. The narrow case where a lie is provable.\n // ---------------------------------------------------------------------------\n bot({\n id: \"forged-googlebot-wrong-ptr\",\n title: \"A forged Googlebot whose address reverse-resolves elsewhere\",\n audience: \"hostile\",\n category: \"impersonation\",\n provenance: \"The commonest forgery: claim the crawler every site allows\",\n notes: \"Proven, not suspected. Google publishes a DNS-based proof and the lookup returns a definitive contradiction.\",\n requests: [{ ...plain(\"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)\"), ip: \"192.0.2.44\" }],\n dns: { reverse: { \"192.0.2.44\": [\"vps-4471.cheap-hosting.example\"] } },\n expect: { verdict: \"confirmed-bot\", botClass: \"impersonator\", certain: true, identity: \"googlebot\", detectors: [\"crawler-verification\"] },\n tags: [\"impersonation\", \"verification\"],\n }),\n bot({\n id: \"forged-googlebot-no-ptr\",\n title: \"A forged Googlebot from an address with no PTR record at all\",\n audience: \"hostile\",\n category: \"impersonation\",\n provenance: \"Every address a verifiable crawler uses has a PTR record; a bare VPS usually does not\",\n notes: \"NXDOMAIN is a definitive answer, unlike a timeout. That distinction is what separates this case from `googlebot-dns-unavailable`.\",\n requests: [{ ...plain(\"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)\"), ip: \"192.0.2.45\" }],\n dns: {},\n expect: { verdict: \"confirmed-bot\", botClass: \"impersonator\", certain: true, identity: \"googlebot\" },\n tags: [\"impersonation\", \"verification\"],\n }),\n bot({\n id: \"forged-googlebot-suffix-trick\",\n title: \"A forgery whose PTR merely contains the operator's domain\",\n audience: \"hostile\",\n category: \"impersonation\",\n provenance: \"googlebot.com.attacker.example resolves under a domain the attacker controls\",\n notes: \"The reason domain matching is on label boundaries and not a substring test.\",\n requests: [{ ...plain(\"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)\"), ip: \"192.0.2.46\" }],\n dns: { reverse: { \"192.0.2.46\": [\"googlebot.com.attacker.example\"] }, forward: { \"googlebot.com.attacker.example\": [\"192.0.2.46\"] } },\n expect: { verdict: \"confirmed-bot\", botClass: \"impersonator\", certain: true },\n tags: [\"impersonation\", \"verification\"],\n }),\n bot({\n id: \"forged-googlebot-forward-mismatch\",\n title: \"A forgery with a PTR it controls that does not resolve back\",\n audience: \"hostile\",\n category: \"impersonation\",\n provenance: \"Anyone controlling reverse DNS for their own address can point it at googlebot.com; only the forward confirmation stops them\",\n requests: [{ ...plain(\"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)\"), ip: \"192.0.2.47\" }],\n dns: { reverse: { \"192.0.2.47\": [\"crawl-1.googlebot.com\"] }, forward: { \"crawl-1.googlebot.com\": [GOOGLEBOT_IP] } },\n expect: { verdict: \"confirmed-bot\", botClass: \"impersonator\", certain: true },\n tags: [\"impersonation\", \"verification\"],\n }),\n bot({\n id: \"forged-bingbot\",\n title: \"A forged bingbot\",\n audience: \"hostile\",\n category: \"impersonation\",\n provenance: \"The same trick against the second most-allowed crawler\",\n requests: [{ ...plain(\"Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)\"), ip: \"192.0.2.48\" }],\n dns: { reverse: { \"192.0.2.48\": [\"static.192-0-2-48.example.net\"] } },\n expect: { verdict: \"confirmed-bot\", botClass: \"impersonator\", certain: true },\n tags: [\"impersonation\"],\n }),\n bot({\n id: \"forged-claudebot\",\n requires: [\"crawler-ranges\"],\n title: \"A forged ClaudeBot from outside the published range\",\n audience: \"hostile\",\n category: \"impersonation\",\n provenance: \"AI crawlers are increasingly allowlisted, which makes them worth forging\",\n requests: [{ ...plain(\"Mozilla/5.0 (compatible; ClaudeBot/1.0; +claudebot@anthropic.com)\"), ip: OUT_OF_RANGE }],\n expect: { verdict: \"confirmed-bot\", botClass: \"impersonator\", certain: true, identity: \"claudebot\" },\n tags: [\"impersonation\", \"verification\"],\n }),\n\n // ---------------------------------------------------------------------------\n // The evasion ladder.\n // ---------------------------------------------------------------------------\n bot({\n id: \"scraper-copied-ua-only\",\n title: \"Level 1: a scraper that copied a Chrome User-Agent and nothing else\",\n audience: \"unwanted-bot\",\n category: \"evasion-ladder\",\n provenance: \"The overwhelming majority of scraping. One header changed, everything else default.\",\n notes:\n \"Missing Accept-Language, missing Sec-Fetch, missing Client Hints, and Accept is */* on a navigation. Four observations — but *not* four independent ones: the first three are absences with one shared benign explanation, a stripping intermediary, so they collapse to their strongest member rather than compounding. That is why this scores in the sixties rather than the nineties, and it is deliberate: the same four absences arrive together from a corporate proxy in front of a real person.\",\n requests: [{ headers: [[\"Host\", \"shop.example\"], [\"User-Agent\", CHROME_UA], [\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"gzip, deflate\"]], protocol: \"https\", httpVersion: \"1.1\" }],\n expect: { verdict: \"suspected-bot\", certain: false, minScore: 60, detectors: [\"header-integrity\"] },\n tags: [\"evasion\"],\n }),\n bot({\n id: \"scraper-copied-headers-wrong-order\",\n title: \"Level 2: a scraper that copied the whole header set but not the order\",\n audience: \"unwanted-bot\",\n category: \"evasion-ladder\",\n provenance: \"Copying headers out of devtools into a dict loses the order, because a dict has none\",\n notes: \"Everything a browser sends, in an order no browser sends it in. Only the ordering rules and the missing hints are left.\",\n requests: [\n {\n headers: [\n [\"Host\", \"shop.example\"],\n [\"Accept-Encoding\", \"gzip, deflate, br\"],\n [\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\"],\n [\"Accept-Language\", \"en-US,en;q=0.9\"],\n [\"User-Agent\", CHROME_UA],\n [\"Sec-Fetch-Site\", \"none\"],\n [\"Sec-Fetch-Mode\", \"navigate\"],\n [\"Sec-Fetch-User\", \"?1\"],\n [\"Sec-Fetch-Dest\", \"document\"],\n [\"Upgrade-Insecure-Requests\", \"1\"],\n [\"Connection\", \"keep-alive\"],\n ],\n protocol: \"https\",\n httpVersion: \"1.1\",\n },\n ],\n expect: { certain: false, detectors: [\"header-order\"], neverAction: [\"drop\"] },\n tags: [\"evasion\"],\n }),\n bot({\n id: \"scraper-perfect-headers\",\n title: \"Level 3: a scraper that copied the header set, the order and the hints\",\n audience: \"unwanted-bot\",\n category: \"evasion-ladder\",\n provenance: \"What a competent scraping stack sends in 2026, using a browser-impersonating TLS library\",\n notes:\n \"Indistinguishable from a browser on a single request, and the library says so: no evidence, verdict unknown. Anything else would be an invented signal. Only behaviour over time can separate this, and behaviour cannot prove anything — which is exactly why it may not block.\",\n requests: [browser(\"chromeWindows\")],\n expect: { verdict: \"unknown\", maxScore: 0 },\n tags: [\"evasion\", \"known-limit\"],\n }),\n bot({\n id: \"scraper-perfect-headers-machine-paced\",\n title: \"Level 4: the same scraper, enumerating at a machine-perfect rhythm\",\n audience: \"unwanted-bot\",\n category: \"evasion-ladder\",\n provenance: \"Perfect headers, and a request every two seconds to stay under a rate limit\",\n notes:\n \"Rate counting sees nothing — thirty requests over a minute is unremarkable. Cadence sees a coefficient of variation near zero, which no person produces. This is the case where behaviour earns its place.\",\n requests: Array.from({ length: 20 }, (_, index) => ({ ...browser(\"chromeWindows\"), path: `/products/${index + 1}`, atMs: index * 2_000 })),\n expect: { certain: false, detectors: [\"cadence\"], neverAction: [\"drop\"] },\n tags: [\"evasion\"],\n }),\n bot({\n id: \"scraper-perfect-headers-human-paced\",\n title: \"Level 5: perfect headers, human pacing, one page at a time\",\n audience: \"unwanted-bot\",\n category: \"evasion-ladder\",\n provenance: \"A distributed scrape: each address takes a handful of pages at irregular intervals and never returns\",\n notes:\n \"Not detected, and the corpus records that plainly. At this point the difference from a person has stopped being technical — there is no signal left at the HTTP layer. What defeats this is cost, not detection: a proof of work, or an account.\",\n requests: [\n { ...browser(\"chromeWindows\"), path: \"/products/501\", atMs: 0 },\n { ...browser(\"chromeWindows\"), path: \"/products/502\", atMs: 7_400 },\n { ...browser(\"chromeWindows\"), path: \"/products/503\", atMs: 23_100 },\n ],\n expect: { verdict: \"unknown\", maxScore: 30 },\n tags: [\"evasion\", \"known-limit\"],\n }),\n\n // ---------------------------------------------------------------------------\n // Traps: detection by construction rather than by inference.\n // ---------------------------------------------------------------------------\n bot({\n id: \"trap-path-followed\",\n title: \"Following a link hidden from layout and from assistive technology\",\n audience: \"unwanted-bot\",\n category: \"trap\",\n provenance: \"A link rendered off-screen with aria-hidden and tabindex=-1, and disallowed in robots.txt\",\n notes:\n \"The only detector whose false-positive rate does not depend on how well the internet is behaving. It does not model what bots look like; it constructs a situation only a bot can be in.\",\n requests: [{ ...browser(\"chromeWindows\"), path: \"/internal/export.csv\" }],\n expect: { verdict: \"confirmed-bot\", certain: true, detectors: [\"trap\"] },\n }),\n bot({\n id: \"trap-field-filled\",\n title: \"Filling a hidden form field\",\n audience: \"hostile\",\n category: \"trap\",\n provenance: \"A honeypot input that is rendered but unreachable by pointer, keyboard or screen reader\",\n notes: \"Rendering the field and forgetting to register its name with the detector is a mistake that fails silently — which is why this case declares the dependency rather than assuming it.\",\n requires: [\"trap-form-field:company_url\"],\n requests: [{ ...browser(\"chromeWindows\"), method: \"POST\", path: \"/contact?company_url=http%3A%2F%2Fspam.example\" }],\n expect: { verdict: \"confirmed-bot\", certain: true, detectors: [\"trap\"] },\n }),\n\n // ---------------------------------------------------------------------------\n // Attacks. Not this library's job to stop, but its job to describe.\n // ---------------------------------------------------------------------------\n bot({\n id: \"credential-stuffing-browserlike\",\n title: \"Credential stuffing from a browser-shaped client\",\n audience: \"hostile\",\n category: \"attack\",\n provenance: \"Modern stuffing runs through headless browsers with correct headers, at a steady rate\",\n notes:\n \"Nothing here is provable, and that is the point of the `delay` action: a quarter of a second per attempt is imperceptible to a person filling in a form and removes the throughput the attack depends on, while excluding nobody.\",\n requests: Array.from({ length: 12 }, (_, index) => ({ ...browser(\"chromeWindows\"), method: \"POST\", path: \"/login\", atMs: index * 900 })),\n expect: { certain: false, neverAction: [\"drop\"] },\n tags: [\"attack\"],\n }),\n bot({\n id: \"credential-stuffing-scripted\",\n title: \"Credential stuffing from a bare script\",\n audience: \"hostile\",\n category: \"attack\",\n provenance: \"The cheap version, and still the common one\",\n requests: Array.from({ length: 12 }, (_, index) => ({\n headers: [[\"Host\", \"shop.example\"], [\"User-Agent\", \"python-requests/2.32.3\"], [\"Accept-Encoding\", \"gzip, deflate\"], [\"Accept\", \"*/*\"], [\"Content-Type\", \"application/x-www-form-urlencoded\"]] as const,\n method: \"POST\",\n path: \"/login\",\n protocol: \"https\" as const,\n httpVersion: \"1.1\",\n atMs: index * 250,\n })),\n expect: { verdict: \"confirmed-bot\", botClass: \"http-client\", certain: true },\n tags: [\"attack\"],\n }),\n bot({\n id: \"path-enumeration\",\n title: \"Enumerating admin paths\",\n audience: \"hostile\",\n category: \"attack\",\n provenance: \"Content discovery against a list of common paths\",\n requests: [\"/admin\", \"/wp-admin\", \"/.env\", \"/.git/config\", \"/phpmyadmin\", \"/backup.zip\", \"/config.json\", \"/api/v1/users\", \"/actuator/env\", \"/server-status\"].map((path, index) => ({\n ...plain(\"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\"),\n path,\n atMs: index * 80,\n })),\n expect: { certain: false, neverAction: [\"drop\"] },\n notes: \"This library classifies traffic; it does not recognise attack payloads. For that, put a honeypot or a WAF alongside it.\",\n tags: [\"attack\", \"known-limit\"],\n }),\n bot({\n id: \"http2-connection-header\",\n title: \"A hand-assembled HTTP/2 request carrying a connection-specific header\",\n audience: \"hostile\",\n category: \"protocol-abuse\",\n provenance: \"RFC 9113 §8.2.2 forbids connection-specific header fields in HTTP/2 and requires endpoints to treat them as malformed\",\n notes: \"One of very few deterministic signals available from a single request: every compliant client honours this, so a violation means the request was assembled by something that does not implement the protocol.\",\n requests: [{ headers: [[\"Host\", \"shop.example\"], [\"User-Agent\", CHROME_UA], [\"Accept\", \"*/*\"], [\"Connection\", \"keep-alive\"], [\"Transfer-Encoding\", \"chunked\"]], protocol: \"https\", httpVersion: \"2.0\" }],\n expect: { verdict: \"confirmed-bot\", certain: true, detectors: [\"header-integrity\"] },\n }),\n bot({\n id: \"ua-rotation-single-actor\",\n title: \"One actor cycling through a User-Agent list\",\n audience: \"unwanted-bot\",\n category: \"evasion\",\n provenance: \"A scraper rotating identities from a list, from one address\",\n notes:\n \"Only meaningful when the actor key is narrower than an address — behind a NAT this shape is a busy office. The detector that catches it ships disabled for exactly that reason, so the default configuration is expected to miss this.\",\n requests: [\n { ...plain(userAgentOf(\"chromeWindows\")), path: \"/p/1\", atMs: 0 },\n { ...plain(userAgentOf(\"firefoxWindows\")), path: \"/p/2\", atMs: 400 },\n { ...plain(userAgentOf(\"safariMac\")), path: \"/p/3\", atMs: 800 },\n { ...plain(userAgentOf(\"edgeWindows\")), path: \"/p/4\", atMs: 1_200 },\n { ...plain(userAgentOf(\"chromeAndroid\")), path: \"/p/5\", atMs: 1_600 },\n ],\n expect: { certain: false, neverAction: [\"drop\"] },\n tags: [\"evasion\", \"known-limit\"],\n }),\n\n // ---------------------------------------------------------------------------\n // Fabricated User-Agents. Not a copied one — an assembled one.\n //\n // A scraping stack that reaches for a \"random user agent\" package gets a string\n // drawn from independent lists of browsers, versions and platforms, and nothing in\n // the package checks that the combination is a build that shipped. The result reads\n // convincingly to a human eye and describes a client that has never existed.\n // ---------------------------------------------------------------------------\n bot({\n id: \"ua-forged-chrome-on-iphone\",\n title: \"A randomised User-Agent claiming Chrome on an iPhone\",\n audience: \"unwanted-bot\",\n category: \"ua-forgery\",\n provenance: \"The commonest output of a UA-randomiser: a desktop Chrome token glued to an iOS platform block\",\n notes:\n \"Apple requires every iOS browser to use the system WebKit, so Chrome for iOS reports CriOS and an AppleWebKit build of 605.1.15. A string with iPhone, Chrome/ and AppleWebKit/537.36 in it is describing a browser Apple does not permit to exist. Still not `certain`: a person running a UA-spoofing extension produces fabricated strings too.\",\n requests: [{ ...plain(\"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.36\", [[\"Accept-Language\", \"en-US,en;q=0.9\"], [\"Accept-Encoding\", \"gzip, deflate, br\"]]) }],\n expect: { certain: false, detectors: [\"ua-coherence\"], neverAction: [\"block\", \"drop\"] },\n tags: [\"evasion\", \"ua-forgery\"],\n }),\n bot({\n id: \"ua-forged-two-platforms\",\n title: \"A User-Agent naming Windows and macOS at once\",\n audience: \"unwanted-bot\",\n category: \"ua-forgery\",\n provenance: \"A template concatenated with a platform block that was already there\",\n requests: [{ ...plain(\"Mozilla/5.0 (Windows NT 10.0; Win64; x64; Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36\", [[\"Accept-Language\", \"en-GB,en;q=0.9\"], [\"Accept-Encoding\", \"gzip, deflate, br\"]]) }],\n expect: { certain: false, detectors: [\"ua-coherence\"], neverAction: [\"block\", \"drop\"] },\n tags: [\"evasion\", \"ua-forgery\"],\n }),\n bot({\n id: \"ua-forged-gecko-in-webkit\",\n title: \"A User-Agent claiming Firefox with an AppleWebKit engine\",\n audience: \"unwanted-bot\",\n category: \"ua-forgery\",\n provenance: \"A Chromium template with the browser token swapped for Firefox and the engine block left alone\",\n notes: \"Gecko has never reported AppleWebKit. Firefox on iOS is WebKit and says FxiOS, never Firefox/.\",\n requests: [{ ...plain(\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Firefox/141.0\", [[\"Accept-Language\", \"en-US,en;q=0.5\"], [\"Accept-Encoding\", \"gzip, deflate, br\"]]) }],\n expect: { certain: false, detectors: [\"ua-coherence\"], neverAction: [\"block\", \"drop\"] },\n tags: [\"evasion\", \"ua-forgery\"],\n }),\n bot({\n id: \"ua-stranded-chrome-on-windows-7\",\n title: \"Current Chrome on a Windows release Chrome no longer supports\",\n audience: \"unwanted-bot\",\n category: \"ua-forgery\",\n provenance: \"Randomised platform blocks still carry Windows NT 6.1 long after Chrome stopped shipping for it\",\n notes:\n \"The weakest thing `ua-coherence` reports, and deliberately so. Google's last Windows 7 release was Chrome 109, but Supermium and Thorium ship current Chromium on retired Windows to a real if small population — so this is `moderate`, it is one signal among several, and on its own it does nothing but tag.\",\n requests: [{ ...browser(\"chromeWindows\"), headers: browser(\"chromeWindows\").headers.map((header) => (header[0] === \"User-Agent\" ? ([\"User-Agent\", \"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36\"] as const) : header)) }],\n expect: { certain: false, detectors: [\"ua-coherence\"], maxScore: 45, neverAction: [\"block\", \"drop\"] },\n tags: [\"evasion\", \"ua-forgery\", \"known-cost\"],\n }),\n\n // ---------------------------------------------------------------------------\n // Wordlists. What the request asks for, rather than who is asking.\n //\n // These carry a real browser's header set, in a real browser's order, with the\n // Client Hints intact — everything the consistency detectors read is correct,\n // because the client copied it correctly. What gives them away is that no link on\n // any site points at what they are asking for.\n // ---------------------------------------------------------------------------\n bot({\n id: \"probe-env-file\",\n title: \"A header-perfect client asking for /.env\",\n audience: \"hostile\",\n category: \"wordlist-probe\",\n provenance: \"The single most-requested path on the internet that no site intends to serve\",\n notes: \"Never `certain`. A URL is client-supplied text, and the client supplying it might be a security engineer testing their own site — which is why the answer is a challenge rather than a closed door.\",\n requests: [{ ...browser(\"chromeWindows\"), path: \"/.env\" }],\n expect: { certain: false, detectors: [\"probe-signature\"], neverAction: [\"block\", \"drop\"] },\n tags: [\"scanning\"],\n }),\n bot({\n id: \"probe-git-config\",\n title: \"Walking a version-control directory\",\n audience: \"hostile\",\n category: \"wordlist-probe\",\n provenance: \"Exposed .git directories are harvested continuously; the config file names the remote\",\n requests: [\n { ...browser(\"chromeWindows\"), path: \"/.git/config\", atMs: 0 },\n { ...browser(\"chromeWindows\"), path: \"/.git/HEAD\", atMs: 900 },\n ],\n expect: { certain: false, detectors: [\"probe-signature\"], neverAction: [\"block\", \"drop\"] },\n tags: [\"scanning\"],\n }),\n bot({\n id: \"probe-log4shell-parameter\",\n title: \"A JNDI lookup in a query parameter\",\n audience: \"hostile\",\n category: \"wordlist-probe\",\n provenance: \"CVE-2021-44228 scanning has never stopped; the payload is sprayed into every parameter and header a crawler can reach\",\n requests: [{ ...browser(\"chromeWindows\"), path: \"/search?q=%24%7Bjndi%3Aldap%3A%2F%2Fscanner.example%2Fa%7D\" }],\n expect: { certain: false, detectors: [\"probe-signature\"], neverAction: [\"block\", \"drop\"] },\n tags: [\"scanning\"],\n }),\n bot({\n id: \"probe-trace-method\",\n title: \"A TRACE request\",\n audience: \"hostile\",\n category: \"wordlist-probe\",\n provenance: \"Cross-site tracing checks whether a server echoes the request back, including headers a script cannot read\",\n requests: [{ ...plain(CHROME_UA), method: \"TRACE\" }],\n expect: { certain: false, detectors: [\"probe-signature\"], neverAction: [\"block\", \"drop\"] },\n tags: [\"scanning\"],\n }),\n\n // ---------------------------------------------------------------------------\n // Framing. A message that disagrees with itself about where it ends.\n // ---------------------------------------------------------------------------\n bot({\n id: \"smuggling-content-length-and-transfer-encoding\",\n title: \"A request carrying both Content-Length and Transfer-Encoding\",\n audience: \"hostile\",\n category: \"protocol-abuse\",\n provenance: \"RFC 9112 §6.1 requires a message with both to be treated as malformed; the disagreement between two servers in a chain is the mechanism of request smuggling\",\n notes: \"Proven, like the HTTP/2 case above and for the same reason: every implementation removes one before sending, so a message with both was framed by hand.\",\n requests: [\n {\n headers: [[\"Host\", \"shop.example\"], [\"User-Agent\", CHROME_UA], [\"Accept\", \"*/*\"], [\"Content-Type\", \"application/x-www-form-urlencoded\"], [\"Content-Length\", \"6\"], [\"Transfer-Encoding\", \"chunked\"]],\n method: \"POST\",\n protocol: \"https\",\n httpVersion: \"1.1\",\n },\n ],\n expect: { verdict: \"confirmed-bot\", certain: true, detectors: [\"header-integrity\"] },\n tags: [\"protocol\"],\n }),\n bot({\n id: \"smuggling-duplicate-host\",\n title: \"A request with two Host headers\",\n audience: \"hostile\",\n category: \"protocol-abuse\",\n provenance: \"RFC 9112 §3.2 permits exactly one Host field; a second makes the request target ambiguous between hops\",\n requests: [\n {\n headers: [[\"Host\", \"shop.example\"], [\"Host\", \"internal.shop.example\"], [\"User-Agent\", CHROME_UA], [\"Accept\", \"*/*\"]],\n protocol: \"https\",\n httpVersion: \"1.1\",\n },\n ],\n expect: { verdict: \"confirmed-bot\", certain: true, detectors: [\"header-integrity\"] },\n tags: [\"protocol\"],\n }),\n];\n","import { plain } from \"./headers.js\";\nimport { bot } from \"./schema.js\";\nimport { BINGBOT_IP, BINGBOT_PTR, GOOGLEBOT_IP, GOOGLEBOT_PTR, IN_RANGE } from \"./ranges.js\";\nimport type { CaseRequest, Expectation, TrafficCase } from \"./schema.js\";\n\n/**\n * Automation almost every site wants to keep serving.\n *\n * Search crawlers bring the traffic. Link unfurlers render the share cards. Monitors\n * tell you the site is up. Feed readers are how a chunk of your audience actually\n * reads you. Getting any of these wrong is not a security incident — it is a slow,\n * quiet loss that shows up weeks later as a ranking drop or a dead preview, with\n * nothing in the logs pointing at the cause.\n *\n * Most declare themselves honestly, which is why `self-identified` reaches `certain`\n * on them: believing a client's own statement about itself cannot misclassify an\n * honest one. Only a handful publish a *verifiable* identity, and those are the only\n * ones that can reach `verified-bot`.\n */\n\n/** A crawler that names itself and can be taken at its word, but not confirmed. */\nfunction declared(id: string, title: string, category: string, userAgent: string, provenance: string, identity: string, extra?: Partial<TrafficCase>): TrafficCase {\n return bot({\n id,\n title,\n audience: \"benign-bot\",\n category,\n provenance,\n requests: [plain(userAgent)],\n expect: { verdict: \"confirmed-bot\", certain: true, identity, detectors: [\"self-identified\"] },\n ...extra,\n });\n}\n\nconst CRAWLER_HEADERS: CaseRequest[\"headers\"] = [\n [\"Host\", \"shop.example\"],\n [\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"],\n [\"Accept-Encoding\", \"gzip, deflate, br\"],\n [\"From\", \"googlebot(at)googlebot.com\"],\n];\n\nconst VERIFIED: Expectation = { verdict: \"verified-bot\", certain: true, detectors: [\"crawler-verification\"] };\n\nexport const BENIGN_BOT_CASES: TrafficCase[] = [\n // ---------------------------------------------------------------------------\n // Search crawlers whose identity can actually be confirmed. These are the cases\n // that must reach `verified-bot`, because a policy that allows verified crawlers\n // is worthless if verification never succeeds.\n // ---------------------------------------------------------------------------\n bot({\n id: \"googlebot-verified\",\n title: \"Googlebot, confirmed by forward-confirmed reverse DNS\",\n audience: \"benign-bot\",\n category: \"search-crawler\",\n provenance: \"Google documents FCrDNS under googlebot.com as the verification method\",\n requests: [{ headers: [[\"User-Agent\", \"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)\"], ...CRAWLER_HEADERS], ip: GOOGLEBOT_IP, protocol: \"https\", httpVersion: \"1.1\" }],\n dns: { reverse: { [GOOGLEBOT_IP]: [GOOGLEBOT_PTR] }, forward: { [GOOGLEBOT_PTR]: [GOOGLEBOT_IP] } },\n expect: { ...VERIFIED, identity: \"googlebot\" },\n tags: [\"verification\"],\n }),\n bot({\n id: \"googlebot-smartphone-verified\",\n title: \"Googlebot Smartphone, confirmed by DNS\",\n audience: \"benign-bot\",\n category: \"search-crawler\",\n provenance: \"Google crawls mobile-first; the UA embeds a full Android Chrome string\",\n requests: [\n {\n headers: [[\"User-Agent\", \"Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/W.X.Y.Z Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)\"], ...CRAWLER_HEADERS],\n ip: GOOGLEBOT_IP,\n protocol: \"https\",\n httpVersion: \"1.1\",\n },\n ],\n dns: { reverse: { [GOOGLEBOT_IP]: [GOOGLEBOT_PTR] }, forward: { [GOOGLEBOT_PTR]: [GOOGLEBOT_IP] } },\n expect: { ...VERIFIED, identity: \"googlebot\" },\n notes: \"The UA embeds a complete Chrome string. Nothing may read that half and conclude the client is a browser.\",\n tags: [\"verification\"],\n }),\n bot({\n id: \"bingbot-verified\",\n title: \"Bingbot, confirmed by DNS under search.msn.com\",\n audience: \"benign-bot\",\n category: \"search-crawler\",\n provenance: \"Microsoft documents FCrDNS under search.msn.com\",\n requests: [{ headers: [[\"User-Agent\", \"Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)\"], ...CRAWLER_HEADERS], ip: BINGBOT_IP, protocol: \"https\", httpVersion: \"1.1\" }],\n dns: { reverse: { [BINGBOT_IP]: [BINGBOT_PTR] }, forward: { [BINGBOT_PTR]: [BINGBOT_IP] } },\n expect: { ...VERIFIED, identity: \"bingbot\" },\n tags: [\"verification\"],\n }),\n bot({\n id: \"googlebot-dns-unavailable\",\n title: \"Googlebot when the resolver is not answering\",\n audience: \"benign-bot\",\n category: \"search-crawler\",\n provenance: \"A DNS outage must not turn every crawler into an accused forgery\",\n notes:\n \"The single most important negative case in the corpus. Silence is not disproof: the verdict falls back to the honest self-declaration, and the crawler is never accused of impersonation.\",\n // A different address from the verified case on purpose: DNS answers are cached\n // by address, so reusing it would resolve from the cache and never exercise the\n // unavailable path at all.\n requests: [{ headers: [[\"User-Agent\", \"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)\"], ...CRAWLER_HEADERS], ip: \"66.249.66.99\", protocol: \"https\", httpVersion: \"1.1\" }],\n dns: { unavailable: true },\n expect: { verdict: \"confirmed-bot\", botClass: \"declared-bot\", certain: true, identity: \"googlebot\", notDetectors: [\"crawler-verification\"] },\n tags: [\"verification\", \"regression\"],\n }),\n bot({\n id: \"duckduckbot-in-range\",\n requires: [\"crawler-ranges\"],\n title: \"DuckDuckBot from its published range\",\n audience: \"benign-bot\",\n category: \"search-crawler\",\n provenance: \"DuckDuckGo publishes an address list rather than PTR records\",\n requests: [{ ...plain(\"Mozilla/5.0 (compatible; DuckDuckBot-Https/1.1; https://duckduckgo.com/duckduckbot)\"), ip: IN_RANGE[\"duckduckbot\"]! }],\n expect: { ...VERIFIED, identity: \"duckduckbot\" },\n tags: [\"verification\"],\n }),\n\n // ---------------------------------------------------------------------------\n // Search crawlers taken at their word.\n // ---------------------------------------------------------------------------\n declared(\"yandexbot\", \"YandexBot\", \"search-crawler\", \"Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)\", \"Yandex publishes FCrDNS under yandex.ru/net/com\", \"yandexbot\"),\n declared(\"baiduspider\", \"Baiduspider\", \"search-crawler\", \"Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)\", \"Baidu publishes FCrDNS under baidu.com\", \"baiduspider\"),\n declared(\"applebot\", \"Applebot\", \"search-crawler\", \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15 (Applebot/0.1; +http://www.apple.com/go/applebot)\", \"Powers Siri and Spotlight suggestions\", \"applebot\"),\n declared(\"seznambot\", \"SeznamBot\", \"search-crawler\", \"Mozilla/5.0 (compatible; SeznamBot/4.0; +http://napoveda.seznam.cz/seznambot-intro/)\", \"The dominant search engine in Czechia\", \"seznambot\"),\n declared(\"naver-yeti\", \"Naver Yeti\", \"search-crawler\", \"Mozilla/5.0 (compatible; Yeti/1.1; +http://naver.me/spd)\", \"The dominant search engine in South Korea\", \"naver-yeti\"),\n declared(\"petalbot\", \"PetalBot\", \"search-crawler\", \"Mozilla/5.0 (Linux; Android 7.0;) AppleWebKit/537.36 (KHTML, like Gecko) Mobile Safari/537.36 (compatible; PetalBot;+https://webmaster.petalsearch.com/site/petalbot)\", \"Huawei's search crawler\", \"petalbot\"),\n declared(\"qwantbot\", \"Qwantbot\", \"search-crawler\", \"Mozilla/5.0 (compatible; Qwantbot/1.0; +https://help.qwant.com/bot/)\", \"A privacy-focused European search engine\", \"qwantbot\"),\n declared(\"mojeekbot\", \"MojeekBot\", \"search-crawler\", \"Mozilla/5.0 (compatible; MojeekBot/0.11; +https://www.mojeek.com/bot.html)\", \"An independent index — the kind of crawler a blanket block quietly kills\", \"mojeek\"),\n declared(\"marginalia\", \"Marginalia Search\", \"search-crawler\", \"Mozilla/5.0 (compatible; Mozilla/5.0; +https://search.marginalia.nu/)\", \"A small independent index\", \"marginalia\"),\n declared(\"sogou\", \"Sogou Spider\", \"search-crawler\", \"Sogou web spider/4.0(+http://www.sogou.com/docs/help/webmasters.htm#07)\", \"A major Chinese search engine\", \"sogou\"),\n\n // Google's specialist fleet. Blocking these breaks Search Console, Ads and\n // Merchant Center in ways that are hard to trace back to a bot rule.\n declared(\"google-inspectiontool\", \"Google-InspectionTool\", \"search-crawler\", \"Mozilla/5.0 (compatible; Google-InspectionTool/1.0;)\", \"What Search Console's URL Inspection uses; blocking it breaks your own diagnostics\", \"googlebot\"),\n declared(\"storebot-google\", \"Storebot-Google\", \"search-crawler\", \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 (compatible; Storebot-Google/1.0; +http://www.google.com/bot.html)\", \"Crawls product pages for Google Shopping and Merchant Center listings\", \"googlebot\"),\n declared(\"googleother\", \"GoogleOther\", \"search-crawler\", \"Mozilla/5.0 (compatible; GoogleOther)\", \"Google's internal one-off crawls, separated from Search\", \"googlebot\"),\n declared(\"adsbot-google\", \"AdsBot-Google\", \"advertising\", \"AdsBot-Google (+http://www.google.com/adsbot.html)\", \"Checks landing-page quality; blocking it degrades ad quality scores\", \"adsbot-google\"),\n declared(\"mediapartners-google\", \"Mediapartners-Google\", \"advertising\", \"Mediapartners-Google\", \"Crawls pages carrying AdSense units to choose relevant ads\", \"adsbot-google\"),\n declared(\"feedfetcher-google\", \"Feedfetcher-Google\", \"feed-reader\", \"FeedFetcher-Google; (+http://www.google.com/feedfetcher.html)\", \"Fetches feeds for Google products\", \"feedfetcher\"),\n\n // ---------------------------------------------------------------------------\n // Link unfurlers. Every one of these renders a share card somewhere; blocking\n // them turns every shared link into a bare URL.\n // ---------------------------------------------------------------------------\n bot({\n id: \"facebook-external-hit-in-range\",\n requires: [\"crawler-ranges\"],\n title: \"facebookexternalhit from Meta's published range\",\n audience: \"benign-bot\",\n category: \"link-unfurler\",\n provenance: \"Meta publishes its crawler ranges; no usable PTR records\",\n requests: [{ ...plain(\"facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)\"), ip: IN_RANGE[\"facebook-external\"]! }],\n expect: { ...VERIFIED, identity: \"facebook-external\" },\n tags: [\"verification\"],\n }),\n declared(\"twitterbot\", \"Twitterbot\", \"link-unfurler\", \"Twitterbot/1.0\", \"Renders X/Twitter card previews\", \"twitterbot\"),\n declared(\"linkedinbot\", \"LinkedInBot\", \"link-unfurler\", \"LinkedInBot/1.0 (compatible; Mozilla/5.0; Apache-HttpClient +http://www.linkedin.com)\", \"Renders LinkedIn share previews. Note it also names Apache-HttpClient — a client that is honest about being built on a library.\", \"linkedinbot\"),\n declared(\"slackbot-linkexpanding\", \"Slackbot-LinkExpanding\", \"link-unfurler\", \"Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)\", \"Unfurls links pasted into Slack channels\", \"slackbot\"),\n declared(\"slackbot-imgproxy\", \"Slack image proxy\", \"link-unfurler\", \"Slack-ImgProxy (+https://api.slack.com/robots)\", \"Fetches images for Slack previews\", \"slackbot\"),\n declared(\"discordbot\", \"Discordbot\", \"link-unfurler\", \"Mozilla/5.0 (compatible; Discordbot/2.0; +https://discordapp.com)\", \"Renders Discord embeds\", \"discordbot\"),\n declared(\"telegrambot\", \"TelegramBot\", \"link-unfurler\", \"TelegramBot (like TwitterBot)\", \"Renders Telegram link previews\", \"telegrambot\"),\n declared(\"whatsapp-preview\", \"WhatsApp link preview\", \"link-unfurler\", \"WhatsApp/2.24.17.79 A\", \"Fetches Open Graph tags when a link is shared in a chat\", \"whatsapp\"),\n declared(\"redditbot\", \"Redditbot\", \"link-unfurler\", \"Mozilla/5.0 (compatible; redditbot/1.0; +http://www.reddit.com/feedback)\", \"Renders Reddit link previews\", \"redditbot\"),\n declared(\"pinterestbot\", \"Pinterestbot\", \"link-unfurler\", \"Mozilla/5.0 (compatible; Pinterestbot/1.0; +http://www.pinterest.com/bot.html)\", \"Powers Pin previews and rich pins\", \"pinterestbot\"),\n declared(\"mastodon-preview\", \"Mastodon link preview\", \"link-unfurler\", \"http.rb/5.1.1 (Mastodon/4.3.1; +https://mastodon.social/)\", \"Fediverse servers fetch previews individually, so one shared link can arrive from hundreds of hosts at once\", \"mastodon\"),\n declared(\"embedly\", \"Embedly\", \"link-unfurler\", \"Mozilla/5.0 (compatible; Embedly/0.2; +http://support.embed.ly/)\", \"Powers previews for many products that do not fetch links themselves\", \"embedly\"),\n\n // ---------------------------------------------------------------------------\n // Monitoring. Usually yours. If any of these is blocked you find out during an\n // incident, from a monitor that was reporting a false green.\n // ---------------------------------------------------------------------------\n bot({\n id: \"uptimerobot-in-range\",\n requires: [\"crawler-ranges\"],\n title: \"UptimeRobot from its published range\",\n audience: \"benign-bot\",\n category: \"monitoring\",\n provenance: \"UptimeRobot publishes its checking addresses\",\n requests: [{ ...plain(\"Mozilla/5.0+(compatible; UptimeRobot/2.0; http://www.uptimerobot.com/)\"), ip: IN_RANGE[\"uptimerobot\"]! }],\n expect: { ...VERIFIED, identity: \"uptimerobot\" },\n tags: [\"verification\"],\n }),\n declared(\"pingdom\", \"Pingdom\", \"monitoring\", \"Mozilla/5.0 (compatible; Pingdom.com_bot_version_1.4_(http://www.pingdom.com/))\", \"Synthetic uptime checks\", \"pingdom\"),\n declared(\"statuscake\", \"StatusCake\", \"monitoring\", \"Mozilla/5.0 (compatible; StatusCake)\", \"Synthetic uptime checks from a distributed pool\", \"statuscake\"),\n declared(\"better-uptime\", \"Better Stack\", \"monitoring\", \"Better Uptime Bot Mozilla/5.0 (compatible; BetterStack/1.0; +https://betterstack.com)\", \"Synthetic uptime checks, formerly Better Uptime\", \"betteruptime\"),\n declared(\"site24x7\", \"Site24x7\", \"monitoring\", \"Mozilla/5.0 (compatible; Site24x7/1.0; +https://www.site24x7.com)\", \"Synthetic uptime and transaction monitoring\", \"site24x7\"),\n declared(\"lighthouse\", \"Chrome Lighthouse\", \"monitoring\", \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36 Chrome-Lighthouse\", \"Performance auditing, often run by your own CI\", \"google-pagespeed\"),\n declared(\"gtmetrix\", \"GTmetrix\", \"monitoring\", \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 GTmetrix\", \"Performance testing on request\", \"gtmetrix\"),\n declared(\"datadog-synthetics\", \"Datadog Synthetics\", \"monitoring\", \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Datadog/Synthetics\", \"Synthetic API and browser checks\", \"datadog\"),\n\n // ---------------------------------------------------------------------------\n // Feeds and podcasts. A real slice of a publisher's audience arrives this way,\n // and none of it runs JavaScript.\n // ---------------------------------------------------------------------------\n declared(\"feedly\", \"Feedly\", \"feed-reader\", \"Feedly/1.0 (+http://www.feedly.com/fetcher.html; 870 subscribers; like FeedFetcher-Google)\", \"Podnews RSS user-agent list; the subscriber count is real information a publisher wants\", \"feedly\"),\n declared(\"inoreader\", \"Inoreader\", \"feed-reader\", \"Inoreader/1.0 (+http://www.inoreader.com/feed-fetcher; 195 subscribers; )\", \"Podnews RSS user-agent list\", \"inoreader\"),\n declared(\"freshrss\", \"FreshRSS\", \"feed-reader\", \"FreshRSS/1.29.1 (Linux; https://freshrss.org)\", \"Podnews RSS user-agent list; self-hosted, so it arrives from a subscriber's own server\", \"freshrss\"),\n declared(\"netnewswire\", \"NetNewsWire\", \"feed-reader\", \"NetNewsWire (RSS Reader; https://netnewswire.com/)\", \"Podnews RSS user-agent list\", \"netnewswire\"),\n declared(\"miniflux\", \"Miniflux\", \"feed-reader\", \"Mozilla/5.0 (compatible; Miniflux/2.2.3; +https://miniflux.app)\", \"Self-hosted feed reader; arrives from a subscriber's own server rather than a vendor's\", \"rss-reader\"),\n declared(\"overcast\", \"Overcast\", \"podcast-client\", \"Overcast/1.0 Podcast Sync (210 subscribers; feed-id=735461; +http://overcast.fm/)\", \"Podnews RSS user-agent list\", \"overcast\", { audience: \"benign-bot\" }),\n declared(\"pocketcasts\", \"Pocket Casts\", \"podcast-client\", \"PocketCasts/1.0 (Pocket Casts Feed Parser; +http://pocketcasts.com/)\", \"Podnews RSS user-agent list\", \"pocketcasts\"),\n declared(\"antennapod\", \"AntennaPod\", \"podcast-client\", \"AntennaPod/3.12.0\", \"Podnews RSS user-agent list\", \"antennapod\"),\n declared(\"podcastaddict\", \"Podcast Addict\", \"podcast-client\", \"PodcastAddict/v5 (+https://podcastaddict.com/; Android podcast app)\", \"Podnews RSS user-agent list\", \"podcastaddict\"),\n\n bot({\n id: \"spotify-podcast-fetch\",\n title: \"Spotify fetching a podcast feed\",\n audience: \"benign-bot\",\n category: \"podcast-client\",\n provenance: \"Podnews RSS user-agent list records Spotify's fetcher as the bare token 'Spotify/1.0'\",\n notes:\n \"A two-token User-Agent with no contact URL and no recognisable product. It is honest automation from a major platform that nonetheless looks like a hand-rolled script — which is exactly why an unrecognised bare token is `strong` rather than `certain`.\",\n requests: [plain(\"Spotify/1.0\")],\n expect: { verdict: \"suspected-bot\", botClass: \"http-client\", certain: false, neverAction: [\"drop\"] },\n }),\n bot({\n id: \"apple-podcasts-fetch\",\n title: \"Apple Podcasts fetching a feed\",\n audience: \"benign-bot\",\n category: \"podcast-client\",\n provenance: \"Podnews records Apple Podcasts as 'iTMS'\",\n notes: \"Four characters, no version, no contact. Legible only because the database has an entry for it — and a reminder that honest automation is under no obligation to be legible.\",\n requests: [plain(\"iTMS\")],\n expect: { verdict: \"confirmed-bot\", certain: true, identity: \"apple-podcasts\", neverAction: [\"drop\"] },\n }),\n\n // ---------------------------------------------------------------------------\n // Archives.\n // ---------------------------------------------------------------------------\n declared(\"internet-archive\", \"Internet Archive\", \"archive\", \"Mozilla/5.0 (compatible; archive.org_bot +http://archive.org/details/archive.org_bot)\", \"The Wayback Machine, which preserves pages long after the site that served them\", \"ia-archiver\"),\n declared(\"heritrix\", \"Heritrix\", \"archive\", \"Mozilla/5.0 (compatible; heritrix/3.4.0 +https://webrecorder.net)\", \"The crawler behind many institutional web archives\", \"heritrix\"),\n];\n","import { crawler } from \"./headers.js\";\nimport { bot } from \"./schema.js\";\nimport type { TrafficCase } from \"./schema.js\";\n\n/**\n * Search and discovery crawlers from outside the anglophone web.\n *\n * A corpus assembled from one developer's own logs is a corpus of one market. These\n * crawlers index the search engines that most of the world actually uses — Naver in\n * Korea, Seznam in Czechia, Yandex across the CIS, Baidu and Sogou in China, Coccoc\n * in Vietnam — and blocking them is invisible from a London office and catastrophic\n * for a business that sells into those markets.\n *\n * Nearly all follow the same convention: a `bot` or `spider` word, and a contact\n * address prefixed with `+`. That pairing is a declaration, which is why the library\n * reaches a proven verdict on clients it has never heard of.\n */\n\nfunction declaredCrawler(id: string, title: string, userAgent: string, provenance: string, options: { identity?: string; from?: string; notes?: string; audience?: TrafficCase[\"audience\"] } = {}): TrafficCase {\n return bot({\n id,\n title,\n audience: options.audience ?? \"benign-bot\",\n category: \"regional-crawler\",\n provenance,\n ...(options.notes !== undefined ? { notes: options.notes } : {}),\n requests: [crawler(userAgent, options.from !== undefined ? { from: options.from } : {})],\n expect: {\n verdict: \"confirmed-bot\",\n certain: true,\n detectors: [\"self-identified\"],\n ...(options.identity !== undefined ? { identity: options.identity } : {}),\n },\n });\n}\n\nexport const REGIONAL_CRAWLER_CASES: TrafficCase[] = [\n // ---- East Asia ----\n declaredCrawler(\"rc-naver-yeti\", \"Naver Yeti\", \"Mozilla/5.0 (compatible; Yeti/1.1; +https://naver.me/spd)\", \"The dominant search engine in South Korea; Naver's share there exceeds Google's\", { identity: \"naver-yeti\" }),\n declaredCrawler(\"rc-naver-yeti-mobile\", \"Naver Yeti, mobile crawl\", \"Mozilla/5.0 (Linux; U; Android 11; ko-kr;) AppleWebKit/537.36 (KHTML, like Gecko) Mobile Safari/537.36 (compatible; Yeti-Mobile/0.1; +https://naver.me/spd)\", \"Naver crawls mobile-first, like Google\", { identity: \"naver-yeti\" }),\n declaredCrawler(\"rc-daum\", \"Daumoa\", \"Mozilla/5.0 (compatible; Daumoa/4.0; +https://cs.daum.net/faq/15/4118.html)\", \"Kakao's search crawler, the second Korean index\"),\n declaredCrawler(\"rc-baidu-mobile\", \"Baiduspider, mobile crawl\", \"Mozilla/5.0 (Linux;u;Android 4.2.2;zh-cn;) AppleWebKit/534.46 (KHTML,like Gecko) Version/5.1 Mobile Safari/10600.6.3 (compatible; Baiduspider-render/2.0; +http://www.baidu.com/search/spider.html)\", \"Baidu's rendering crawler; note the frozen Android 4.2.2 device string\", { identity: \"baiduspider\" }),\n declaredCrawler(\"rc-baidu-image\", \"Baiduspider-image\", \"Baiduspider-image+(+http://www.baidu.com/search/spider.htm)\", \"Baidu's image crawler uses a bare, unusual User-Agent with a doubled plus\", { identity: \"baiduspider\" }),\n declaredCrawler(\"rc-sogou-inst\", \"Sogou inst spider\", \"Sogou inst spider/4.0(+http://www.sogou.com/docs/help/webmasters.htm#07)\", \"Sogou's instant-answer crawler; a major Chinese index owned by Tencent\", { identity: \"sogou\" }),\n declaredCrawler(\"rc-360-spider\", \"360Spider\", \"Mozilla/5.0 (compatible; 360Spider/1.0; +http://www.so.com/help/help_3_2.html)\", \"Qihoo 360's search crawler, a significant Chinese index\", {}),\n declaredCrawler(\"rc-shenma\", \"YisouSpider\", \"Mozilla/5.0 (Linux; U; Android 12; zh-CN;) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Mobile Safari/537.36 (compatible; YisouSpider/5.0; +http://www.yisou.com/help_center.html)\", \"Alibaba's Shenma mobile search crawler, large in Chinese mobile search\", {}),\n declaredCrawler(\"rc-bytedance-toutiao\", \"Bytedance search crawler\", \"Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.117 Mobile Safari/537.36 (compatible; Bytespider; spider-feedback@bytedance.com)\", \"ByteDance's crawler, feeding Toutiao search and model training\", { identity: \"bytespider\", audience: \"unwanted-bot\", notes: \"Widely reported for aggressive rates and inconsistent robots.txt compliance. Honest about who it is, all the same.\" }),\n\n // ---- The CIS and Eastern Europe ----\n declaredCrawler(\"rc-yandex-images\", \"YandexImages\", \"Mozilla/5.0 (compatible; YandexImages/3.0; +http://yandex.com/bots)\", \"Yandex runs a fleet of specialised crawlers under one verification domain\", { identity: \"yandexbot\" }),\n declaredCrawler(\"rc-yandex-mobile\", \"YandexMobileBot\", \"Mozilla/5.0 (iPhone; CPU iPhone OS 8_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12B411 Safari/600.1.4 (compatible; YandexMobileBot/3.0; +http://yandex.com/bots)\", \"Yandex's mobile-first crawler\", { identity: \"yandexbot\" }),\n declaredCrawler(\"rc-yandex-accessibility\", \"YandexAccessibilityBot\", \"Mozilla/5.0 (compatible; YandexAccessibilityBot/3.0; +http://yandex.com/bots)\", \"Checks pages for accessibility problems on Yandex's behalf\", { identity: \"yandexbot\" }),\n declaredCrawler(\"rc-mail-ru\", \"Mail.RU_Bot\", \"Mozilla/5.0 (compatible; Linux x86_64; Mail.RU_Bot/2.0; +http://go.mail.ru/help/robots)\", \"The crawler behind Mail.ru's search and its social previews\", {}),\n declaredCrawler(\"rc-seznam-mobile\", \"SeznamBot mobile\", \"Mozilla/5.0 (compatible; SeznamBot/4.0-mobile; +http://napoveda.seznam.cz/seznambot-intro/)\", \"Seznam holds a substantial share of Czech search\", { identity: \"seznambot\" }),\n\n // ---- Southeast and South Asia ----\n declaredCrawler(\"rc-coccoc\", \"Coccoc bot\", \"Mozilla/5.0 (compatible; coccocbot-web/1.0; +http://help.coccoc.com/searchengine)\", \"Coc Coc is a major Vietnamese search engine and browser\", {}),\n declaredCrawler(\"rc-coccoc-image\", \"Coccoc image bot\", \"Mozilla/5.0 (compatible; coccocbot-image/1.0; +http://help.coccoc.com/searchengine)\", \"Coc Coc's image crawler\", {}),\n declaredCrawler(\"rc-petal-mobile\", \"PetalBot, mobile crawl\", \"Mozilla/5.0 (Linux; Android 7.0;) AppleWebKit/537.36 (KHTML, like Gecko) Mobile Safari/537.36 (compatible; PetalBot;+https://webmaster.petalsearch.com/site/petalbot)\", \"Huawei's Petal Search, the default on Huawei devices outside Google's ecosystem\", { identity: \"petalbot\" }),\n\n // ---- Europe and the Americas ----\n declaredCrawler(\"rc-qwant\", \"Qwantbot\", \"Mozilla/5.0 (compatible; Qwantbot/1.0; +https://help.qwant.com/bot/)\", \"A French privacy-focused engine; blocking it removes a European alternative from your reach\", { identity: \"qwantbot\" }),\n declaredCrawler(\"rc-mojeek\", \"MojeekBot\", \"Mozilla/5.0 (compatible; MojeekBot/0.11; +https://www.mojeek.com/bot.html)\", \"A British independent index — one of very few crawlers building an index from scratch\", { identity: \"mojeek\" }),\n declaredCrawler(\"rc-startpage\", \"Startpage\", \"Mozilla/5.0 (compatible; StartpageBot/1.0; +https://www.startpage.com/robot)\", \"A Dutch privacy-preserving front end\", {}),\n declaredCrawler(\"rc-ecosia\", \"Ecosia\", \"Mozilla/5.0 (compatible; EcosiaBot/1.0; +https://ecosia.org/bot)\", \"A German search engine that plants trees with its ad revenue\", {}),\n declaredCrawler(\"rc-brave-search\", \"Brave Search\", \"Mozilla/5.0 (compatible; BraveSearchBot/1.0; +https://search.brave.com/help/brave-search-crawler)\", \"Brave builds its own index rather than reselling another engine's\", {}),\n declaredCrawler(\"rc-kagi\", \"Kagi Search\", \"Mozilla/5.0 (compatible; Kagibot/1.0; +https://kagi.com/bot)\", \"A subscription search engine whose crawler is small but whose users are paying customers\", {}),\n declaredCrawler(\"rc-stract\", \"Stract\", \"Mozilla/5.0 (compatible; StractBot/0.2; open source search engine; +https://trystract.com/webmasters)\", \"An open-source independent index\", {}),\n declaredCrawler(\"rc-right-dao\", \"RightDao\", \"Mozilla/5.0 (compatible; RightDaoBot/1.0; +https://rightdao.com/bot)\", \"A small independent index\", {}),\n declaredCrawler(\"rc-gigablast\", \"Gigablast\", \"Mozilla/5.0 (compatible; GigablastOpenSource/1.0; +http://www.gigablast.com/spider.html)\", \"A long-running open-source crawler\", {}),\n\n // ---- Specialist Google and Microsoft fleets ----\n declaredCrawler(\"rc-googlebot-image\", \"Googlebot-Image\", \"Googlebot-Image/1.0\", \"Google's image crawler sends a bare product token with no contact URL at all\", { identity: \"googlebot\", notes: \"Recognised only because a signature knows the token. Google's own fleet does not consistently follow the contact convention, which is a useful corrective to any rule that assumes crawlers are legible.\" }),\n declaredCrawler(\"rc-googlebot-news\", \"Googlebot-News\", \"Googlebot-News\", \"Google News indexing; another bare token\", { identity: \"googlebot\" }),\n declaredCrawler(\"rc-googlebot-video\", \"Googlebot-Video\", \"Googlebot-Video/1.0\", \"Google's video crawler\", { identity: \"googlebot\" }),\n declaredCrawler(\"rc-google-favicon\", \"Google Favicon\", \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Google Favicon\", \"Fetches favicons for search results and bookmarks\", { identity: \"googlebot\", notes: \"A full Chrome User-Agent with a two-word suffix and none of the headers Chrome sends. Before a signature existed for it this scored 90 as an impersonator — a legitimate Google fetcher, one token away from being blocked.\" }),\n declaredCrawler(\"rc-google-read-aloud\", \"Google Read Aloud\", \"Mozilla/5.0 (Linux; Android 7.0;) AppleWebKit/537.36 (KHTML, like Gecko) Mobile Safari/537.36 (compatible; Google-Read-Aloud; +https://developers.google.com/search/docs/crawling-indexing/overview-google-crawlers)\", \"Fetches pages so Assistant can read them aloud — an accessibility surface\", { identity: \"googlebot\", notes: \"Blocking this removes a page from a text-to-speech surface that some people rely on to read the web at all.\" }),\n declaredCrawler(\"rc-google-site-verification\", \"Google Site Verification\", \"Mozilla/5.0 (compatible; Google-Site-Verification/1.0)\", \"Confirms ownership during Search Console setup; blocking it blocks your own onboarding\", { identity: \"googlebot\" }),\n declaredCrawler(\"rc-bing-preview\", \"BingPreview\", \"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534+ (KHTML, like Gecko) BingPreview/1.0b\", \"Renders page snapshots for Bing results; note the unusual 'AppleWebKit/534+' version\", { identity: \"bingbot\" }),\n declaredCrawler(\"rc-adidxbot\", \"adidxbot\", \"Mozilla/5.0 (compatible; adidxbot/2.0; +http://www.bing.com/bingbot.htm)\", \"Microsoft Advertising's landing-page crawler\", { identity: \"bingbot\" }),\n declaredCrawler(\"rc-msnbot-media\", \"MSNBot-Media\", \"msnbot-media/1.1 (+http://search.msn.com/msnbot.htm)\", \"Microsoft's media crawler, still using the historic msnbot name\", { identity: \"bingbot\" }),\n declaredCrawler(\"rc-apple-siri\", \"Applebot for Siri\", \"Mozilla/5.0 (Device; CPU OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1 (Applebot/0.1; +http://www.apple.com/go/applebot)\", \"Powers Siri suggestions and Spotlight; note the literal word 'Device' where a model would be\", { identity: \"applebot\" }),\n];\n","import { crawler } from \"./headers.js\";\nimport { bot } from \"./schema.js\";\nimport type { TrafficCase } from \"./schema.js\";\n\n/**\n * Crawlers with a job other than general search.\n *\n * Price comparison, job aggregation, travel metasearch, academic indexing, news\n * syndication, brand protection, accessibility auditing. Most sites want some of\n * these and not others, and which is which is a commercial question rather than a\n * technical one — a price comparator is a distribution channel to one retailer and a\n * competitor's research tool to another.\n *\n * The library's job here is only to name them accurately enough that a rule can pick\n * sides. Every case therefore asserts identification, and none asserts an action.\n */\n\nfunction vertical(\n id: string,\n title: string,\n category: string,\n userAgent: string,\n provenance: string,\n options: { audience?: TrafficCase[\"audience\"]; identity?: string; notes?: string; accept?: string } = {},\n): TrafficCase {\n return bot({\n id,\n title,\n audience: options.audience ?? \"declared-bot\",\n category,\n provenance,\n ...(options.notes !== undefined ? { notes: options.notes } : {}),\n requests: [crawler(userAgent, options.accept !== undefined ? { accept: options.accept } : {})],\n expect: {\n verdict: \"confirmed-bot\",\n certain: true,\n detectors: [\"self-identified\"],\n ...(options.identity !== undefined ? { identity: options.identity } : {}),\n },\n });\n}\n\nexport const VERTICAL_CRAWLER_CASES: TrafficCase[] = [\n // ---- Shopping and price comparison ----\n vertical(\"vc-google-merchant\", \"Google Merchant Center feed fetch\", \"commerce-crawler\", \"Mozilla/5.0 (compatible; Google-Shopping-Quality; +http://www.google.com/bot.html)\", \"Validates product feeds and landing pages for Shopping listings; blocking it suspends your listings\", { identity: \"googlebot\", audience: \"benign-bot\" }),\n vertical(\"vc-idealo\", \"Idealo price comparison\", \"commerce-crawler\", \"Mozilla/5.0 (compatible; idealo-bot/1.0; +https://www.idealo.de/robots)\", \"The largest price comparison site in Germany; a distribution channel for many retailers\", {}),\n vertical(\"vc-kelkoo\", \"Kelkoo\", \"commerce-crawler\", \"Mozilla/5.0 (compatible; KelkooBot/1.0; +https://www.kelkoo.com/bot)\", \"European shopping comparison\", {}),\n vertical(\"vc-pricerunner\", \"PriceRunner\", \"commerce-crawler\", \"Mozilla/5.0 (compatible; PriceRunnerBot/1.0; +https://www.pricerunner.com/robot)\", \"Nordic and UK price comparison\", {}),\n vertical(\"vc-shopping-feed\", \"A shopping feed aggregator\", \"commerce-crawler\", \"Mozilla/5.0 (compatible; ShoppingFeedBot/2.1; +https://feeds.example/crawler)\", \"Feed aggregators fetch a product XML on a schedule and then verify the landing pages\", {}),\n vertical(\"vc-honey-coupon\", \"A coupon extension's backend\", \"commerce-crawler\", \"Mozilla/5.0 (compatible; CouponFinderBot/1.4; +https://coupons.example/bot)\", \"Browser coupon extensions test discount codes at checkout from their own servers\", { audience: \"unwanted-bot\", notes: \"Runs on behalf of a real shopper, from a server, against your checkout. Neither clearly wanted nor clearly hostile — exactly the kind of case a policy has to decide about deliberately.\" }),\n vertical(\"vc-stock-tracker\", \"An availability tracker\", \"commerce-crawler\", \"Mozilla/5.0 (compatible; StockAlertBot/3.0; +https://stockalert.example/about-our-bot)\", \"Restock trackers poll product pages continuously on behalf of subscribers\", { audience: \"unwanted-bot\" }),\n\n // ---- Jobs, property and travel ----\n vertical(\"vc-indeed\", \"Indeed's job crawler\", \"vertical-crawler\", \"Mozilla/5.0 (compatible; Indeedbot/1.1; +http://www.indeed.com/indeedbot.html)\", \"Aggregates job postings; for a recruiter it is free distribution\", { audience: \"benign-bot\" }),\n vertical(\"vc-adzuna\", \"Adzuna\", \"vertical-crawler\", \"Mozilla/5.0 (compatible; AdzunaBot/1.0; +https://www.adzuna.co.uk/bot)\", \"Job aggregation across Europe\", { audience: \"benign-bot\" }),\n vertical(\"vc-trivago\", \"Trivago's rate crawler\", \"vertical-crawler\", \"Mozilla/5.0 (compatible; TrivagoBot/1.0; +https://www.trivago.com/bot)\", \"Hotel metasearch fetching live rates\", {}),\n vertical(\"vc-skyscanner\", \"Skyscanner\", \"vertical-crawler\", \"Mozilla/5.0 (compatible; SkyscannerBot/1.0; +https://www.skyscanner.net/bot)\", \"Flight metasearch fetching live fares from airline and agency sites\", {}),\n vertical(\"vc-property-portal\", \"A property portal's listing crawler\", \"vertical-crawler\", \"Mozilla/5.0 (compatible; PropertyIndexBot/2.3; +https://property.example/crawler)\", \"Property portals crawl agent sites to keep listings in sync\", {}),\n\n // ---- News and syndication ----\n vertical(\"vc-newsblur\", \"NewsBlur\", \"feed-reader\", \"NewsBlur Feed Fetcher - 412 subscribers - https://www.newsblur.com/site/9124/shop-example (Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7))\", \"NewsBlur names the subscriber count and the exact feed page, which is genuinely useful information\", { audience: \"benign-bot\", notes: \"Note the nested Mozilla string inside the parentheses. Publishers can and do use the subscriber count to decide how much a feed reader is worth serving.\" }),\n vertical(\"vc-google-news\", \"Google News publisher crawl\", \"news-crawler\", \"Mozilla/5.0 (compatible; Googlebot-News; +http://www.google.com/bot.html)\", \"News indexing runs on a separate crawl budget from web search\", { identity: \"googlebot\", audience: \"benign-bot\" }),\n vertical(\"vc-apple-news\", \"Apple News\", \"news-crawler\", \"Mozilla/5.0 (compatible; AppleNewsBot/1.0; +http://www.apple.com/go/applebot)\", \"Fetches articles for the Apple News channel\", { identity: \"applebot\", audience: \"benign-bot\" }),\n vertical(\"vc-smartnews\", \"SmartNews\", \"news-crawler\", \"Mozilla/5.0 (compatible; SmartNewsBot/1.0; +https://www.smartnews.com/bot)\", \"A very large news aggregator in Japan and the United States\", { audience: \"benign-bot\" }),\n vertical(\"vc-flipboard\", \"Flipboard\", \"news-crawler\", \"Mozilla/5.0 (compatible; FlipboardProxy/1.1; +http://flipboard.com/browserproxy)\", \"Fetches and re-renders articles for Flipboard's reader\", { audience: \"benign-bot\" }),\n vertical(\"vc-podcast-index\", \"Podcast Index\", \"feed-reader\", \"Mozilla/5.0 (compatible; PodcastIndexBot/1.0; +https://podcastindex.org/bot)\", \"An open podcast directory crawling feeds\", { audience: \"benign-bot\" }),\n\n // ---- Academic and research ----\n vertical(\"vc-semantic-scholar\", \"Semantic Scholar\", \"research-crawler\", \"Mozilla/5.0 (compatible; SemanticScholarBot/1.0; +https://www.semanticscholar.org/crawler)\", \"The Allen Institute's academic index\", { audience: \"benign-bot\" }),\n vertical(\"vc-crossref\", \"Crossref\", \"research-crawler\", \"Mozilla/5.0 (compatible; CrossrefBot/1.0; mailto:labs@crossref.org)\", \"DOI registration and metadata; note the mailto contact rather than a URL\", { audience: \"benign-bot\" }),\n vertical(\"vc-openalex\", \"OpenAlex\", \"research-crawler\", \"Mozilla/5.0 (compatible; OpenAlexBot/1.0; +https://openalex.org/bot; mailto:support@openalex.org)\", \"An open catalogue of scholarly works\", { audience: \"benign-bot\" }),\n vertical(\"vc-webis\", \"A university research crawl\", \"research-crawler\", \"Mozilla/5.0 (compatible; WebisBot/1.0; +https://webis.de/crawler.html; research crawl, please contact us for exclusion)\", \"Academic web-science groups crawl at scale and generally honour exclusion requests promptly\", { audience: \"benign-bot\", notes: \"Research crawls are usually run by people who will stop if you ask. An email is often faster and cheaper than a block rule.\" }),\n vertical(\"vc-common-crawl-news\", \"Common Crawl's news crawl\", \"research-crawler\", \"CCBot/2.0 (https://commoncrawl.org/faq/)\", \"The news subset of Common Crawl, refreshed continuously\", { identity: \"ccbot\" }),\n\n // ---- Accessibility, compliance and quality ----\n vertical(\"vc-axe-monitor\", \"An accessibility auditing crawler\", \"compliance-crawler\", \"Mozilla/5.0 (compatible; AccessibilityMonitorBot/4.0; +https://a11y-monitor.example/bot)\", \"Automated WCAG auditing, often run under a legal compliance programme\", { audience: \"benign-bot\", notes: \"Frequently commissioned by the site owner and then forgotten about. Blocking it makes an accessibility report look clean by removing the evidence.\" }),\n vertical(\"vc-siteimprove\", \"Siteimprove\", \"compliance-crawler\", \"Mozilla/5.0 (compatible; SiteimproveBot/2.0; +https://siteimprove.com/bot)\", \"Quality and accessibility auditing for large organisations\", { audience: \"benign-bot\" }),\n vertical(\"vc-w3c-validator\", \"The W3C validator\", \"compliance-crawler\", \"W3C_Validator/1.3 http://validator.w3.org/services\", \"Somebody clicked Validate on your page\", { audience: \"benign-bot\" }),\n vertical(\"vc-ssl-labs\", \"SSL Labs\", \"compliance-crawler\", \"Mozilla/5.0 (compatible; SSL Labs/1.0; +https://www.ssllabs.com/about/assessment.html)\", \"TLS configuration assessment, usually run by the site's own operator\", { audience: \"benign-bot\" }),\n\n // ---- Brand protection and enforcement ----\n vertical(\"vc-brand-protection\", \"A brand-protection crawler\", \"enforcement-crawler\", \"Mozilla/5.0 (compatible; BrandProtectBot/2.0; +https://brandprotect.example/crawler)\", \"Looks for counterfeit listings and trademark misuse\", {}),\n vertical(\"vc-copyright-scan\", \"A copyright enforcement crawler\", \"enforcement-crawler\", \"Mozilla/5.0 (compatible; CopyrightScanBot/1.2; +https://rights.example/bot)\", \"Rights-holder agents scan for infringing copies\", {}),\n vertical(\"vc-domain-monitor\", \"A domain and certificate monitor\", \"enforcement-crawler\", \"Mozilla/5.0 (compatible; DomainWatchBot/1.0; +https://domainwatch.example/bot)\", \"Watches for lookalike domains and certificate issuance\", {}),\n\n // ---- Archival and preservation ----\n vertical(\"vc-archive-today\", \"archive.today\", \"archive-crawler\", \"Mozilla/5.0 (compatible; archive.today; +http://archive.today/legal)\", \"On-demand page archiving triggered by a person pasting a URL\", { audience: \"benign-bot\" }),\n vertical(\"vc-perma-cc\", \"Perma.cc\", \"archive-crawler\", \"Mozilla/5.0 (compatible; perma.cc; +https://perma.cc/about)\", \"Harvard's citation-preservation service, used heavily by courts and journals\", { audience: \"benign-bot\" }),\n vertical(\"vc-national-library\", \"A national library web archive\", \"archive-crawler\", \"Mozilla/5.0 (compatible; heritrix/3.4.0 +https://www.bl.uk/collection-guides/uk-web-archive)\", \"Legal-deposit archiving; in several countries a library has a statutory right to crawl\", { identity: \"heritrix\", audience: \"benign-bot\" }),\n vertical(\"vc-conifer\", \"Webrecorder\", \"archive-crawler\", \"Mozilla/5.0 (compatible; Webrecorder/2.0; +https://webrecorder.net/crawler)\", \"High-fidelity archiving of dynamic pages\", { audience: \"benign-bot\" }),\n\n // ---- Security and reputation ----\n vertical(\"vc-safe-browsing\", \"Google Safe Browsing\", \"reputation-crawler\", \"Mozilla/5.0 (compatible; Google-Safety; +http://www.google.com/bot.html)\", \"Checks pages for malware and phishing; blocking it risks an unresolvable warning interstitial\", { identity: \"googlebot\", audience: \"benign-bot\", notes: \"One of the few crawlers where blocking has a direct, visible cost to your own visitors: a Safe Browsing warning is shown to every Chrome user.\" }),\n vertical(\"vc-virustotal\", \"VirusTotal URL scan\", \"reputation-crawler\", \"Mozilla/5.0 (compatible; VirusTotalCloud/1.0; +https://www.virustotal.com/bot)\", \"Somebody submitted your URL for scanning\", { audience: \"benign-bot\" }),\n vertical(\"vc-urlscan\", \"urlscan.io\", \"reputation-crawler\", \"Mozilla/5.0 (compatible; urlscan.io/1.0; +https://urlscan.io/about/)\", \"Renders a page in a sandbox and publishes the result\", { audience: \"benign-bot\" }),\n vertical(\"vc-phishtank\", \"PhishTank verification\", \"reputation-crawler\", \"Mozilla/5.0 (compatible; PhishTankBot/1.0; +https://phishtank.org/bot)\", \"Verifies reported phishing URLs\", { audience: \"benign-bot\" }),\n\n // ---- SEO auditing run by the site's own owner ----\n vertical(\"vc-screaming-frog\", \"Screaming Frog\", \"seo-crawler\", \"Screaming Frog SEO Spider/21.4\", \"A desktop crawler; almost always the site's own SEO team auditing their own site\", { audience: \"benign-bot\", notes: \"No contact URL and no vendor infrastructure — it runs from somebody's laptop, so it arrives from a residential address. Blocking it blocks your own audit.\" }),\n vertical(\"vc-sitebulb\", \"Sitebulb\", \"seo-crawler\", \"Mozilla/5.0 (compatible; Sitebulb/6.5; +https://sitebulb.com/bot)\", \"Another desktop SEO auditor\", { audience: \"benign-bot\" }),\n vertical(\"vc-lumar\", \"Lumar\", \"seo-crawler\", \"Mozilla/5.0 (compatible; deepcrawl; +https://www.lumar.io/bot)\", \"Enterprise site auditing, formerly DeepCrawl\", { audience: \"unwanted-bot\" }),\n vertical(\"vc-oncrawl\", \"OnCrawl\", \"seo-crawler\", \"Mozilla/5.0 (compatible; OnCrawl/1.0; +https://www.oncrawl.com/bot)\", \"Enterprise SEO crawling\", { audience: \"unwanted-bot\" }),\n];\n","import { crawler, plain } from \"./headers.js\";\nimport { bot } from \"./schema.js\";\nimport type { TrafficCase } from \"./schema.js\";\n\n/**\n * Advertising verification, and the machinery that opens links before people do.\n *\n * Two categories that share a property worth noticing: both fetch a page **because a\n * person is about to see it**, and neither is that person.\n *\n * Ad verification vendors load your landing pages to check they are what was sold —\n * that the creative rendered, that the page is brand-safe, that the impression was\n * viewable. Blocking them does not protect anything; it fails the verification, and\n * the campaign is what gets paused.\n *\n * Email link scanners are stranger and more consequential. Every enterprise mail\n * gateway rewrites links and fetches them before delivery, which means a link you\n * emailed to one customer arrives at your server first from Microsoft, Proofpoint or\n * Mimecast — often several times, from several regions, before the human clicks. If\n * those fetches are blocked the scanner may mark the link unsafe, and the person\n * never sees your page at all.\n */\n\nfunction fetcher(id: string, title: string, category: string, userAgent: string, provenance: string, options: { notes?: string; audience?: TrafficCase[\"audience\"]; certain?: boolean } = {}): TrafficCase {\n return bot({\n id,\n title,\n audience: options.audience ?? \"benign-bot\",\n category,\n provenance,\n ...(options.notes !== undefined ? { notes: options.notes } : {}),\n requests: [crawler(userAgent)],\n expect: options.certain === false ? { certain: false, neverAction: [\"drop\"] } : { verdict: \"confirmed-bot\", certain: true, detectors: [\"self-identified\"] },\n });\n}\n\nexport const ADVERTISING_EMAIL_CASES: TrafficCase[] = [\n // ---------------------------------------------------------------------------\n // Ad verification, viewability and brand safety.\n // ---------------------------------------------------------------------------\n fetcher(\"adv-doubleverify\", \"DoubleVerify\", \"ad-verification\", \"Mozilla/5.0 (compatible; DoubleVerifyBot/1.0; +https://doubleverify.com/bot)\", \"Verifies that ads rendered on a brand-safe page; its verdict decides whether an impression is billable\"),\n fetcher(\"adv-ias\", \"Integral Ad Science\", \"ad-verification\", \"Mozilla/5.0 (compatible; IAS crawler; +https://integralads.com/site-indexing-policy/)\", \"Brand-safety classification of pages carrying advertising\"),\n fetcher(\"adv-moat\", \"Oracle Moat\", \"ad-verification\", \"Mozilla/5.0 (compatible; MoatBot/1.0; +https://moat.com/bot)\", \"Viewability and attention measurement\"),\n fetcher(\"adv-comscore\", \"Comscore\", \"ad-verification\", \"Mozilla/5.0 (compatible; proximic; +https://www.comscore.com/Web-Crawler)\", \"Contextual classification for audience measurement; the crawler still uses its acquired Proximic name\"),\n fetcher(\"adv-grapeshot\", \"Grapeshot\", \"ad-verification\", \"Mozilla/5.0 (compatible; GrapeshotCrawler/2.0; +http://www.grapeshot.co.uk/crawler.php)\", \"Contextual keyword classification, now part of Oracle\"),\n fetcher(\"adv-peer39\", \"Peer39\", \"ad-verification\", \"Mozilla/5.0 (compatible; Peer39Bot/1.0; +https://www.peer39.com/bot)\", \"Page-level contextual categorisation for advertisers\"),\n fetcher(\"adv-adbeat\", \"Adbeat\", \"ad-verification\", \"Mozilla/5.0 (compatible; Adbeat_Bot; +https://www.adbeat.com/operation_policy)\", \"Competitive advertising intelligence\", { audience: \"unwanted-bot\" }),\n fetcher(\"adv-adsbot-landing\", \"AdsBot checking a landing page\", \"ad-verification\", \"AdsBot-Google-Mobile (+http://www.google.com/mobile/adsbot.html)\", \"Google's mobile landing-page quality checker; blocking it lowers ad quality scores directly\"),\n fetcher(\"adv-bing-ads\", \"Microsoft Advertising landing check\", \"ad-verification\", \"Mozilla/5.0 (compatible; adidxbot/2.0; +http://www.bing.com/bingbot.htm)\", \"The equivalent for Microsoft Advertising\"),\n fetcher(\"adv-criteo\", \"Criteo\", \"ad-verification\", \"Mozilla/5.0 (compatible; CriteoBot/0.1; +https://www.criteo.com/criteo-crawler/)\", \"Retargeting; crawls product pages to build creatives\"),\n fetcher(\"adv-taboola\", \"Taboola\", \"ad-verification\", \"Mozilla/5.0 (compatible; TaboolaBot/1.0; +https://www.taboola.com/bot)\", \"Content recommendation crawling\"),\n fetcher(\"adv-outbrain\", \"Outbrain\", \"ad-verification\", \"Mozilla/5.0 (compatible; OutbrainBot/1.0; +https://www.outbrain.com/bot)\", \"Content recommendation crawling\"),\n fetcher(\"adv-pubmatic\", \"PubMatic\", \"ad-verification\", \"Mozilla/5.0 (compatible; PubMaticBot/1.0; +https://pubmatic.com/bot)\", \"Supply-side platform verifying inventory\"),\n fetcher(\"adv-ttd\", \"The Trade Desk\", \"ad-verification\", \"Mozilla/5.0 (compatible; TTD-Content/1.0; +https://www.thetradedesk.com/general/crawler)\", \"Demand-side contextual crawling\"),\n\n // ---------------------------------------------------------------------------\n // Email link protection. Fires before a person clicks, sometimes long before.\n // ---------------------------------------------------------------------------\n bot({\n id: \"email-microsoft-safelinks\",\n title: \"Microsoft Defender Safe Links\",\n audience: \"benign-bot\",\n category: \"email-link-scanner\",\n provenance: \"Microsoft 365 rewrites every link in inbound mail and fetches it at delivery time and again at click time\",\n notes:\n \"Almost every corporate recipient sits behind this. If a marketing email goes to ten thousand Microsoft 365 mailboxes, your server sees ten thousand fetches from Microsoft before a single person clicks — and blocking them can mark the link unsafe, so nobody ever arrives.\",\n requests: [crawler(\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 BingPreview/1.0b\")],\n expect: { verdict: \"confirmed-bot\", certain: true, identity: \"bingbot\" },\n }),\n fetcher(\"email-proofpoint\", \"Proofpoint URL Defense\", \"email-link-scanner\", \"Mozilla/5.0 (compatible; ProofpointURLDefenseBot/1.0; +https://www.proofpoint.com/us/threat-reference/url-defense)\", \"Rewrites and pre-fetches links in enterprise mail\"),\n fetcher(\"email-mimecast\", \"Mimecast URL Protect\", \"email-link-scanner\", \"Mozilla/5.0 (compatible; MimecastURLProtectBot/1.0; +https://www.mimecast.com/products/url-protect/)\", \"The same mechanism, very common in the UK and Australia\"),\n fetcher(\"email-barracuda\", \"Barracuda Link Protection\", \"email-link-scanner\", \"Mozilla/5.0 (compatible; BarracudaLinkProtectBot/1.0; +https://www.barracuda.com/link-protection)\", \"Mail-gateway link scanning\"),\n fetcher(\"email-cisco-esa\", \"Cisco Secure Email\", \"email-link-scanner\", \"Mozilla/5.0 (compatible; CiscoSecureEmailBot/1.0; +https://www.cisco.com/go/emailsecurity)\", \"Outbreak filters fetch URLs before delivery\"),\n fetcher(\"email-google-safe\", \"Gmail link scanning\", \"email-link-scanner\", \"Mozilla/5.0 (compatible; Google-Safety; +http://www.google.com/bot.html)\", \"Gmail checks links against Safe Browsing before showing them\"),\n fetcher(\"email-slack-unfurl\", \"Slack unfurling a link posted in a channel\", \"email-link-scanner\", \"Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)\", \"Fires the instant somebody pastes a URL, before anyone opens it\"),\n fetcher(\"email-teams-unfurl\", \"Microsoft Teams unfurling a link\", \"email-link-scanner\", \"Mozilla/5.0 (compatible; MicrosoftPreview/2.0; +https://aka.ms/MicrosoftPreview)\", \"Teams renders its own preview cards\"),\n fetcher(\"email-zoom-preview\", \"Zoom chat link preview\", \"email-link-scanner\", \"Mozilla/5.0 (compatible; ZoomBot/1.0; +https://zoom.us/bot)\", \"Chat clients preview links the same way mail gateways scan them\"),\n bot({\n id: \"email-newsletter-open-tracker\",\n title: \"An email client fetching a tracking pixel\",\n audience: \"infrastructure\",\n category: \"email-link-scanner\",\n provenance: \"Apple Mail Privacy Protection pre-fetches every remote image in every message, from Apple's own infrastructure, whether or not the message is opened\",\n notes:\n \"Why open rates stopped meaning anything. From a server's point of view this is a fetch with no person attached, at a time nobody chose, from an address that belongs to neither party.\",\n requests: [plain(\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)\", [[\"Accept\", \"image/avif,image/webp,image/apng,image/*,*/*;q=0.8\"], [\"Accept-Encoding\", \"gzip, deflate, br\"], [\"Accept-Language\", \"en-GB,en;q=0.9\"]])],\n expect: { certain: false, neverAction: [\"drop\"] },\n }),\n\n // ---------------------------------------------------------------------------\n // Chat and messaging previews, which behave like both of the above.\n // ---------------------------------------------------------------------------\n fetcher(\"preview-signal\", \"Signal link preview\", \"link-unfurler\", \"Mozilla/5.0 (compatible; SignalBot/1.0; +https://signal.org/bot)\", \"Signal generates previews on the sender's device before the message is sent\"),\n fetcher(\"preview-imessage\", \"iMessage rich link\", \"link-unfurler\", \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15 (Applebot/0.1; +http://www.apple.com/go/applebot)\", \"iMessage builds Rich Links using Applebot\"),\n fetcher(\"preview-matrix\", \"A Matrix homeserver preview\", \"link-unfurler\", \"Mozilla/5.0 (compatible; Synapse/1.121.0; +https://matrix.org/docs/spec/)\", \"Every Matrix homeserver generates its own previews, so one link fetches from many servers\"),\n fetcher(\"preview-bluesky\", \"Bluesky link card\", \"link-unfurler\", \"Mozilla/5.0 (compatible; BlueskyBot/1.0; +https://bsky.app/about/bot)\", \"Builds the link card shown in a post\"),\n fetcher(\"preview-threads\", \"Threads link preview\", \"link-unfurler\", \"meta-externalfetcher/1.1 (+https://developers.facebook.com/docs/sharing/webmasters/crawler)\", \"Meta's fetch-on-behalf-of-a-user crawler, shared across its products\"),\n];\n","/**\n * Realistic cookie jars.\n *\n * A browser that has been to a commercial site once is carrying a dozen cookies it\n * never asked for, and their *shape* is as characteristic as any header: Google\n * Analytics' `_ga` encodes a client id and a first-seen timestamp, Meta's `_fbp`\n * encodes a version, a subdomain index and a creation time, a TCF consent string is\n * base64 with a version prefix. Automation carries none of this, or carries a single\n * hand-set session cookie and nothing else.\n *\n * The values below are structurally correct and entirely synthetic. They exist so a\n * fixture looks like a request from somebody who has actually used the web, rather\n * than like `Cookie: sid=abc`.\n */\n\n/** Deterministic pseudo-random digits, so a fixture is stable across runs. */\nfunction digits(seed: string, length: number): string {\n let hash = 0x811c9dc5;\n let out = \"\";\n for (let i = 0; out.length < length; i++) {\n hash ^= seed.charCodeAt(i % seed.length) + i;\n hash = Math.imul(hash, 0x01000193) >>> 0;\n out += String(hash % 1_000_000_000).padStart(9, \"0\");\n }\n return out.slice(0, length);\n}\n\nexport interface CookieJarOptions {\n /** Seed for the synthetic identifiers, so two visitors differ. */\n visitor?: string;\n /** Epoch seconds the visitor was first seen. */\n firstSeen?: number;\n /** Epoch seconds of this session's start. */\n sessionStart?: number;\n /** Include Google Analytics 4 cookies. Present on most of the commercial web. */\n analytics?: boolean;\n /** Include Meta's advertising cookies. */\n advertising?: boolean;\n /** Include a consent-management platform's cookies. */\n consent?: boolean;\n /** Include Cloudflare's bot-management cookies, present on a large slice of sites. */\n cloudflare?: boolean;\n /** Include product-analytics and support-widget cookies. */\n productAnalytics?: boolean;\n /** Application cookies: session, cart, CSRF. */\n application?: boolean;\n /** Extra pairs appended verbatim. */\n extra?: readonly (readonly [string, string])[];\n}\n\n/**\n * Builds a `Cookie` header value.\n *\n * Order matters a little: browsers send cookies sorted by path length descending and\n * then by creation time, which in practice means the host-wide analytics cookies set\n * on the first visit come before the application cookies set later. Reproducing that\n * ordering is the kind of detail a hand-written fixture never has.\n */\nexport function cookieJar(options: CookieJarOptions = {}): string {\n const visitor = options.visitor ?? \"v1\";\n const firstSeen = options.firstSeen ?? 1_753_920_000;\n const sessionStart = options.sessionStart ?? 1_756_544_400;\n const pairs: Array<[string, string]> = [];\n\n if (options.analytics !== false) {\n const clientId = `GA1.1.${digits(`${visitor}ga`, 10)}.${firstSeen}`;\n pairs.push([\"_ga\", clientId]);\n // GA4's per-property cookie: session count, engagement flags, session start.\n pairs.push([\"_ga_QK7X2ZLM4P\", `GS1.1.${sessionStart}.4.1.${sessionStart + 187}.58.0.0`]);\n }\n if (options.advertising === true) {\n pairs.push([\"_fbp\", `fb.1.${firstSeen}000.${digits(`${visitor}fb`, 10)}`]);\n pairs.push([\"_gcl_au\", `1.1.${digits(`${visitor}gcl`, 9)}.${firstSeen}`]);\n }\n if (options.consent === true) {\n pairs.push([\n \"OptanonConsent\",\n `isGpcEnabled=0&datestamp=Sat+Aug+30+2026+09%3A00%3A00+GMT%2B0000&version=202405.1.0&interactionCount=1&groups=C0001%3A1%2CC0002%3A1%2CC0003%3A1%2CC0004%3A0`,\n ]);\n pairs.push([\"euconsent-v2\", \"CQJd8YAQJd8YAAcABBENBhFsAP_gAEPgAAYgKPtV_G__bWlr8X73aftkeY1P9_h77sQxBhfJE-4FzLvW_JwXx2ExNA36tqIKmRIAu3TBIQNlHJDURVCgaogVryDMak2coTNKJ6BkiFMRO2dYCF5vmwtj-QKY5vr991dx2B-t7dr83dzyy4hHn3a5_2a0WJCdA5-tDfv9bROb-9IOd_x8v4v8_F_rE2_eT1l_tWvp7D9-cts7_XW89_fff_9Pn_-uB_-_3_vAAA\"],\n );\n }\n if (options.cloudflare === true) {\n pairs.push([\"__cf_bm\", `${digits(`${visitor}cf`, 22)}.${sessionStart}-1.0.1.1-${digits(`${visitor}cfb`, 40)}`]);\n pairs.push([\"cf_clearance\", `${digits(`${visitor}cfc`, 32)}-${sessionStart}-1.2.1.1-${digits(`${visitor}cfd`, 48)}`]);\n }\n if (options.productAnalytics === true) {\n pairs.push([\"_hjSessionUser_3184920\", `eyJpZCI6IjQ0NmE${digits(`${visitor}hj`, 8)}IiwiY3JlYXRlZCI6MTc1MzkyMDAwMH0=`]);\n pairs.push([\"intercom-id-jf7q2wnx\", `d8${digits(`${visitor}ic`, 6)}-4a1c-9e0b-${digits(`${visitor}ic2`, 12)}`]);\n }\n if (options.application !== false) {\n pairs.push([\"sid\", `s%3A${digits(`${visitor}sid`, 24)}.${digits(`${visitor}sig`, 26)}`]);\n pairs.push([\"csrftoken\", digits(`${visitor}csrf`, 32)]);\n }\n for (const [name, value] of options.extra ?? []) pairs.push([name, value]);\n\n return pairs.map(([name, value]) => `${name}=${value}`).join(\"; \");\n}\n\n/** A first-time visitor: no analytics history, only whatever this page set. */\nexport function freshVisitorJar(visitor = \"new\"): string {\n return cookieJar({ visitor, analytics: false, application: true });\n}\n\n/** A returning customer of a commercial site — the fullest jar in ordinary use. */\nexport function returningCustomerJar(visitor = \"returning\"): string {\n return cookieJar({\n visitor,\n analytics: true,\n advertising: true,\n consent: true,\n cloudflare: true,\n productAnalytics: true,\n application: true,\n extra: [\n [\"cart\", `${digits(`${visitor}cart`, 8)}%3A3items`],\n [\"locale\", \"en-GB\"],\n [\"currency\", \"GBP\"],\n ],\n });\n}\n","import { browser, plain } from \"./headers.js\";\nimport { returningCustomerJar } from \"./cookies.js\";\nimport { bot, human } from \"./schema.js\";\nimport type { Header } from \"./headers.js\";\nimport type { TrafficCase } from \"./schema.js\";\n\n/**\n * The machinery between the client and the origin.\n *\n * CDNs revalidating a cached object, edge functions calling back, API gateways\n * forwarding, service meshes probing, mirrors of your own content. None of it is a\n * person and almost none of it is unwelcome, and the recurring lesson is the same:\n * **the answer is usually `ignorePaths` or the allowlist, not a detector.**\n *\n * This section also carries the cases that matter most for getting the client address\n * right, because everything here adds a forwarding header. `X-Forwarded-For` is\n * client-supplied; trusting it without knowing your topology hands the choice of\n * identity to whoever is sending.\n */\n\nfunction edge(id: string, title: string, userAgent: string, provenance: string, extra: readonly Header[] = [], options: { notes?: string; certain?: boolean } = {}): TrafficCase {\n return bot({\n id,\n title,\n audience: \"infrastructure\",\n category: \"edge-infrastructure\",\n provenance,\n ...(options.notes !== undefined ? { notes: options.notes } : {}),\n requests: [plain(userAgent, [[\"Accept-Encoding\", \"gzip, br\"], ...extra])],\n expect: { neverAction: [\"block\", \"drop\", \"redirect\"] },\n tags: [\"allowlist-candidate\"],\n });\n}\n\nexport const CDN_GATEWAY_CASES: TrafficCase[] = [\n // ---------------------------------------------------------------------------\n // CDN and edge fetchers pulling from origin.\n // ---------------------------------------------------------------------------\n edge(\"edge-cloudflare-origin\", \"Cloudflare fetching from origin\", \"Mozilla/5.0 (compatible; Cloudflare-Traffic-Manager/1.0; +https://www.cloudflare.com/bot)\", \"A CDN revalidating a cached object presents its own identity to the origin\", [[\"CF-Connecting-IP\", \"203.0.113.88\"], [\"CF-Ray\", \"8f2a1c7d4e9b3210-LHR\"], [\"CF-IPCountry\", \"GB\"], [\"X-Forwarded-For\", \"203.0.113.88\"], [\"X-Forwarded-Proto\", \"https\"]], { notes: \"The real client address arrives in CF-Connecting-IP, which only Cloudflare can set. Reading X-Forwarded-For here instead would take whatever the client wrote.\" }),\n edge(\"edge-fastly-shield\", \"Fastly shield-tier fetch\", \"Mozilla/5.0 (compatible; Fastly/1.0)\", \"A shield POP consolidating requests before they reach origin\", [[\"Fastly-Client-IP\", \"198.51.100.44\"], [\"X-Forwarded-For\", \"198.51.100.44, 151.101.1.1\"], [\"Fastly-FF\", \"a1b2c3\"], [\"X-Varnish\", \"912837465\"]]),\n edge(\"edge-akamai\", \"Akamai edge fetch\", \"Mozilla/5.0 (compatible; Akamai/1.0)\", \"Akamai forwards the client address in True-Client-IP\", [[\"True-Client-IP\", \"192.0.2.77\"], [\"X-Forwarded-For\", \"192.0.2.77\"], [\"Akamai-Origin-Hop\", \"2\"], [\"Via\", \"1.1 v1-akamaitech.net(ghost) (AkamaiGHost)\"]]),\n edge(\"edge-cloudfront\", \"CloudFront origin request\", \"Amazon CloudFront\", \"CloudFront identifies itself with a bare product name and adds its own viewer headers\", [[\"X-Amz-Cf-Id\", \"K3sLmN9pQrStUvWxYz01234567890AbCdEfGhIjKlMnOpQrSt==\"], [\"CloudFront-Viewer-Country\", \"DE\"], [\"CloudFront-Is-Mobile-Viewer\", \"false\"], [\"X-Forwarded-For\", \"198.51.100.9\"], [\"Via\", \"2.0 a1b2c3d4.cloudfront.net (CloudFront)\"]], { certain: false }),\n edge(\"edge-bunny\", \"Bunny CDN origin pull\", \"Mozilla/5.0 (compatible; BunnyCDN/1.0)\", \"A smaller CDN pulling an uncached object\", [[\"X-Forwarded-For\", \"203.0.113.5\"], [\"CDN-PullZone\", \"184920\"], [\"CDN-RequestCountryCode\", \"PL\"]]),\n edge(\"edge-vercel\", \"Vercel edge function calling back to the API\", \"Vercel Edge Functions\", \"A serverless edge runtime invoking an origin route\", [[\"X-Vercel-Id\", \"lhr1::iad1::abcde-1756544400123-1a2b3c4d5e6f\"], [\"X-Vercel-IP-Country\", \"GB\"], [\"X-Forwarded-For\", \"203.0.113.201\"]], { certain: false }),\n edge(\"edge-cloudflare-worker\", \"A Cloudflare Worker subrequest\", \"Mozilla/5.0 (compatible; Cloudflare-Workers/1.0)\", \"Workers make subrequests that arrive at origin with their own identity\", [[\"CF-Worker\", \"shop.example\"], [\"X-Forwarded-For\", \"203.0.113.14\"]]),\n\n // ---------------------------------------------------------------------------\n // Gateways, meshes and load balancers.\n // ---------------------------------------------------------------------------\n edge(\"edge-aws-alb-health\", \"An ALB health check\", \"ELB-HealthChecker/2.0\", \"The load balancer deciding whether this target is in service\", [[\"Connection\", \"close\"]], { notes: \"Perfectly regular, from a fixed private address, forever. Letting a bot policy decide whether the load balancer believes you are healthy is how a false positive becomes a rolling restart.\" }),\n edge(\"edge-gcp-health\", \"A Google Cloud health check\", \"GoogleHC/1.0\", \"The equivalent on Google Cloud load balancing\", []),\n edge(\"edge-azure-probe\", \"An Azure Front Door probe\", \"Edge Health Probe\", \"Azure Front Door probing origin health from every edge location it serves from\", []),\n edge(\"edge-envoy-mesh\", \"An Envoy sidecar forwarding within a service mesh\", \"Envoy/HC\", \"Service-mesh health checking between sidecars\", [[\"X-Envoy-Internal\", \"true\"], [\"X-Request-Id\", \"0b4d1c8a-3f2e-4a91-b7c6-52e0d8f19a3b\"], [\"X-Envoy-Expected-Rq-Timeout-Ms\", \"15000\"]], { certain: false }),\n edge(\"edge-istio-probe\", \"An Istio readiness probe\", \"kube-probe/1.32\", \"Kubernetes probing the sidecar rather than the application\", []),\n edge(\"edge-nginx-upstream\", \"An nginx reverse proxy forwarding a browser request\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36\", \"The proxy passes the client's User-Agent through and adds forwarding headers of its own\", [[\"X-Real-IP\", \"203.0.113.130\"], [\"X-Forwarded-For\", \"203.0.113.130\"], [\"X-Forwarded-Proto\", \"https\"], [\"X-Forwarded-Host\", \"shop.example\"]], { certain: false, notes: \"The User-Agent belongs to a person; the connection belongs to the proxy. Getting the address wrong here mislabels a real customer as the proxy, or the proxy as a customer.\" }),\n edge(\"edge-haproxy-forward\", \"HAProxy forwarding with a Forwarded header\", \"curl/8.12.1\", \"The standardised Forwarded header from RFC 7239, still much rarer than X-Forwarded-For\", [[\"Forwarded\", \"for=192.0.2.60;proto=https;by=203.0.113.43\"], [\"X-Forwarded-For\", \"192.0.2.60\"]], { certain: false }),\n edge(\"edge-api-gateway\", \"An API gateway forwarding an authenticated call\", \"AmazonAPIGateway_a1b2c3d4e5\", \"API Gateway rewrites the request entirely before it reaches the integration\", [[\"X-Amzn-Trace-Id\", \"Root=1-68b2a1c0-1a2b3c4d5e6f708192a3b4c5\"], [\"X-Forwarded-For\", \"198.51.100.120\"], [\"X-Forwarded-Port\", \"443\"]], { certain: false }),\n edge(\"edge-kong\", \"Kong forwarding upstream\", \"Kong/3.9.0\", \"An API gateway adding its own trace headers\", [[\"X-Kong-Request-Id\", \"9f8e7d6c5b4a39281706\"], [\"X-Forwarded-For\", \"203.0.113.66\"]], { certain: false }),\n\n // ---------------------------------------------------------------------------\n // Address resolution: the highest-consequence configuration in the library.\n // ---------------------------------------------------------------------------\n human({\n id: \"edge-person-behind-two-proxies\",\n title: \"A person behind a CDN and a reverse proxy\",\n category: \"forwarding\",\n provenance: \"Two hops: the CDN appends the client, the reverse proxy appends the CDN. Reading the wrong entry mislabels a customer.\",\n notes:\n \"The chain is client, then CDN, then proxy. With `trustedProxies` configured the walk stops at the first address that is not yours, which is the customer. With a hop count that is wrong by one it stops at the CDN, and every customer behind that CDN becomes one actor.\",\n requests: [\n {\n ...browser(\"chromeWindows\", { cookie: returningCustomerJar(\"proxied\"), kind: \"same-origin-navigate\", referer: \"https://shop.example/\" }),\n headers: [\n ...browser(\"chromeWindows\", { cookie: returningCustomerJar(\"proxied\"), kind: \"same-origin-navigate\", referer: \"https://shop.example/\" }).headers,\n [\"X-Forwarded-For\", \"203.0.113.210, 198.51.100.1, 10.0.0.7\"],\n [\"X-Forwarded-Proto\", \"https\"],\n [\"Via\", \"1.1 cdn-edge (squid/6.6), 1.1 lb-01\"],\n ],\n path: \"/products/91\",\n },\n ],\n expect: { certain: false },\n }),\n bot({\n id: \"edge-xff-header-injection\",\n title: \"A client writing a forged chain into X-Forwarded-For\",\n audience: \"hostile\",\n category: \"forwarding\",\n provenance: \"Prepending entries is free; the header is whatever the sender types\",\n notes:\n \"Whatever it writes, it is still a bare library client and is classified as one. Address spoofing changes which *actor* the request is attributed to, not what the request is — which is why the classification and the actor key are separate concerns.\",\n requests: [\n {\n headers: [[\"Host\", \"shop.example\"], [\"User-Agent\", \"curl/8.12.1\"], [\"Accept\", \"*/*\"], [\"X-Forwarded-For\", \"66.249.66.1, 8.8.8.8, 1.1.1.1\"], [\"X-Real-IP\", \"66.249.66.1\"], [\"True-Client-IP\", \"66.249.66.1\"], [\"CF-Connecting-IP\", \"66.249.66.1\"]],\n ip: \"192.0.2.222\",\n protocol: \"https\",\n httpVersion: \"1.1\",\n },\n ],\n expect: { verdict: \"confirmed-bot\", botClass: \"http-client\", certain: true },\n tags: [\"security\"],\n }),\n\n // ---------------------------------------------------------------------------\n // Mirrors, caches and other people's infrastructure carrying your content.\n // ---------------------------------------------------------------------------\n edge(\"edge-google-amp\", \"Google AMP cache fetching a page\", \"Mozilla/5.0 (compatible; Google-AMPHTML; +http://www.google.com/bot.html)\", \"The AMP cache re-serves your page from Google's infrastructure\"),\n edge(\"edge-cloudflare-alwaysonline\", \"Cloudflare Always Online archiving a page\", \"Mozilla/5.0 (compatible; CloudflareAlwaysOnline/1.0; +http://www.cloudflare.com/always-online) AppleWebKit/534.34\", \"Cloudflare snapshots pages so it can serve them while your origin is down\"),\n edge(\"edge-wordpress-jetpack\", \"Jetpack fetching from a WordPress site\", \"Jetpack by WordPress.com\", \"Jetpack proxies images and stats through WordPress.com infrastructure\", [], { certain: false }),\n edge(\"edge-imgproxy\", \"An image proxy fetching a source image\", \"imgproxy/3.27.2\", \"Image proxies fetch originals and re-encode them\", [], { certain: false }),\n edge(\"edge-wayback-replay\", \"The Wayback Machine replaying an archived page\", \"Mozilla/5.0 (compatible; archive.org_bot; +http://archive.org/details/archive.org_bot)\", \"Replay fetches missing subresources live from the origin\"),\n edge(\"edge-rss-proxy\", \"A feed proxy normalising a feed\", \"Mozilla/5.0 (compatible; FeedBurner/1.0; +https://feedburner.google.com)\", \"Feed proxies fetch once and fan out to many subscribers\"),\n];\n","import { browser } from \"./headers.js\";\nimport { cookieJar, freshVisitorJar, returningCustomerJar } from \"./cookies.js\";\nimport { human } from \"./schema.js\";\nimport type { ProfileName, ProfileOptions } from \"./headers.js\";\nimport type { TrafficCase } from \"./schema.js\";\n\n/**\n * People, browser by browser.\n *\n * Every current engine on every platform that carries meaningful traffic, each\n * request built with the whole apparatus a real browser sends: the Client Hints\n * block, the Fetch Metadata group, a cookie jar from a visitor who has actually used\n * the web, cache validators on a revisit, `Priority`, and the long tail of\n * conditional headers — `Sec-GPC`, `Save-Data`, `Sec-Purpose`, `Early-Data`,\n * `Sec-CH-Prefers-*`, the Network Information hints.\n *\n * The point of the volume is coverage of *populations*, not of code paths. Amazon\n * Silk, UC Browser, MIUI Browser and QQ Browser between them carry hundreds of\n * millions of people who are invisible in a corpus assembled from a European\n * developer's own devices — and they are precisely the clients that a naive \"modern\n * Chromium sends Client Hints\" check misclassifies.\n */\n\nconst SITE = \"https://shop.example\";\n\ninterface Scenario {\n suffix: string;\n title: string;\n options: ProfileOptions;\n path?: string;\n}\n\n/** One realistic first visit per profile: fresh cookies, no referrer, user-initiated. */\nfunction landing(name: ProfileName, title: string, provenance: string, options: ProfileOptions = {}, notes?: string): TrafficCase {\n return human({\n id: `browse-${kebab(name)}`,\n title,\n category: \"browser-population\",\n provenance,\n ...(notes !== undefined ? { notes } : {}),\n requests: [{ ...browser(name, { cookie: freshVisitorJar(name), ...options }), path: \"/\" }],\n expect: { certain: false, verdict: [\"unknown\", \"human\"] },\n });\n}\n\nfunction kebab(name: string): string {\n return name.replace(/([a-z0-9])([A-Z])/g, \"$1-$2\").toLowerCase();\n}\n\n/** Extra scenarios for the profiles that carry the most traffic. */\nfunction scenarios(name: ProfileName, label: string, list: readonly Scenario[]): TrafficCase[] {\n return list.map((scenario) =>\n human({\n id: `browse-${kebab(name)}-${scenario.suffix}`,\n title: `${label}: ${scenario.title}`,\n category: \"browser-scenario\",\n provenance: \"Header set derived from the engine's documented behaviour for this request kind\",\n requests: [{ ...browser(name, scenario.options), ...(scenario.path !== undefined ? { path: scenario.path } : {}) }],\n expect: { certain: false, verdict: [\"unknown\", \"human\"] },\n }),\n );\n}\n\nconst COMMON: readonly Scenario[] = [\n {\n suffix: \"returning\",\n title: \"a returning customer following an internal link\",\n path: \"/products/1184\",\n options: { kind: \"same-origin-navigate\", referer: `${SITE}/products`, cookie: returningCustomerJar(\"regular\") },\n },\n {\n suffix: \"high-entropy-hints\",\n title: \"after the server asked for high-entropy Client Hints\",\n path: \"/checkout\",\n options: { kind: \"same-origin-navigate\", referer: `${SITE}/basket`, cookie: returningCustomerJar(\"hints\"), highEntropyHints: true, prefers: { colorScheme: \"dark\", reducedMotion: \"no-preference\" } },\n },\n {\n suffix: \"xhr\",\n title: \"an in-page fetch() for JSON\",\n path: \"/api/basket\",\n options: { kind: \"xhr\", cookie: returningCustomerJar(\"xhr\"), origin: SITE },\n },\n {\n suffix: \"reload\",\n title: \"pressing reload on a page it already has cached\",\n path: \"/products/1184\",\n options: { kind: \"navigate\", cookie: returningCustomerJar(\"reload\"), reload: true, revalidate: { etag: 'W/\"6a9-19256f0c1d8\"', modifiedSince: \"Fri, 29 Aug 2026 14:22:10 GMT\" } },\n },\n {\n suffix: \"form-post\",\n title: \"submitting the checkout form\",\n path: \"/checkout/confirm\",\n options: { kind: \"form-post\", referer: `${SITE}/checkout`, origin: SITE, cookie: returningCustomerJar(\"post\"), contentType: \"application/x-www-form-urlencoded\", contentLength: 284 },\n },\n];\n\nconst MOBILE_EXTRAS: readonly Scenario[] = [\n {\n suffix: \"metered\",\n title: \"on a metered connection with data saver on\",\n path: \"/products\",\n options: { kind: \"same-origin-navigate\", referer: `${SITE}/`, cookie: cookieJar({ visitor: \"metered\" }), saveData: true, networkHints: { rtt: 300, downlink: 0.4, ect: \"3g\" } },\n },\n {\n suffix: \"prefetch\",\n title: \"a speculative prefetch while a link is hovered\",\n path: \"/products/2210\",\n options: { kind: \"same-origin-navigate\", referer: `${SITE}/products`, cookie: cookieJar({ visitor: \"prefetch\" }), prefetch: true },\n },\n];\n\nexport const HUMAN_BROWSER_CASES: TrafficCase[] = [\n // ---------------------------------------------------------------------------\n // Chromium desktop\n // ---------------------------------------------------------------------------\n landing(\"chromeWindows\", \"Chrome 152 on Windows 11\", \"2026 User-Agent lists; Chromium freezes the minor version fields to 0.0.0 so the header leaks less\"),\n landing(\"chromeMac\", \"Chrome 152 on an Apple-silicon Mac\", \"2026 User-Agent lists; the platform is reported as macOS through Client Hints while the UA still says Mac OS X 10_15_7\"),\n landing(\"chromeLinux\", \"Chrome 152 on Linux\", \"2026 User-Agent lists. A small population that trips heuristics keyed on desktop operating-system share\"),\n landing(\"chromeChromeOs\", \"Chrome 151 on a Chromebook\", \"CrOS builds carry a board and milestone in the UA and report 'Chrome OS' as the platform hint\"),\n landing(\"edgeWindows\", \"Edge 150 on Windows\", \"Edge reports Chromium brands alongside its own and truncates its version to major.0.0.0 in the UA\"),\n landing(\"edgeMac\", \"Edge 150 on macOS\", \"The same build on a different platform; only the platform hint and UA platform token differ\"),\n landing(\"operaWindows\", \"Opera 135 on Windows\", \"Opera carries an OPR/ token after the Chrome/ token and reports an Opera brand\"),\n landing(\"vivaldiWindows\", \"Vivaldi 7.6 on Windows\", \"Vivaldi appends its own token and reports a Vivaldi brand alongside Chromium\"),\n landing(\n \"braveWindows\",\n \"Brave on Windows with Shields up\",\n \"Brave presents an unmodified Chrome identity by design so that its users are not singled out, and adds Sec-GPC\",\n { gpc: true, dnt: true },\n \"Deliberately indistinguishable from Chrome in the UA. The only tell is Sec-GPC, which is a privacy signal rather than an automation one — reading it as suspicious would invert its purpose.\",\n ),\n landing(\"yandexWindows\", \"Yandex Browser 25.8 on Windows\", \"The dominant browser in Russia; reports YaBrowser and Yowser brands alongside Chromium\"),\n\n // ---------------------------------------------------------------------------\n // Gecko\n // ---------------------------------------------------------------------------\n landing(\"firefoxWindows\", \"Firefox 148 on Windows\", \"2026 User-Agent lists. Gecko implements no Client Hints at all, which is correct and must not read as an omission\"),\n landing(\"firefoxMac\", \"Firefox 148 on macOS\", \"2026 User-Agent lists\"),\n landing(\"firefoxLinux\", \"Firefox 148 on Linux, German locale\", \"2026 User-Agent lists; a four-entry Accept-Language chain is entirely ordinary in Europe\"),\n landing(\"firefoxEsr\", \"Firefox 140 ESR in a managed enterprise fleet\", \"ESR trails the release channel by roughly a year and is what most managed desktops run\"),\n landing(\"firefoxAndroid\", \"Firefox 148 on Android\", \"Gecko on Android reports Mobile in the UA and, like desktop Gecko, sends no Client Hints\"),\n\n // ---------------------------------------------------------------------------\n // WebKit\n // ---------------------------------------------------------------------------\n landing(\"safariMac\", \"Safari 18.7 on macOS\", \"WebKit interleaves the Fetch Metadata headers with content negotiation rather than grouping them\"),\n landing(\"safariIos\", \"Safari 18.7 on an iPhone\", \"2026 User-Agent lists; the Mobile/15E148 build token has been frozen for years\"),\n landing(\"safariIpad\", \"Safari 18.7 on an iPad\", \"iPadOS reports an iPad UA in mobile mode; in desktop-class mode it reports a Macintosh UA instead\"),\n landing(\"chromeIos\", \"Chrome on iOS\", \"CriOS is WebKit underneath — iOS permits no other engine — so its header order is Safari's, not Chromium's\", {}, \"A client whose User-Agent says Chrome and whose header order says Safari. Both are true, and a naive engine-versus-order consistency check would call it a forgery.\"),\n landing(\"firefoxIos\", \"Firefox on iOS\", \"FxiOS is likewise WebKit; the Gecko name in the product token describes the brand, not the engine\"),\n landing(\"edgeIos\", \"Edge on iOS\", \"EdgiOS is WebKit too; iOS permits no other engine, so every browser there shares Safari's header order\"),\n landing(\"duckduckgoIos\", \"DuckDuckGo browser on iOS\", \"Appends a DuckDuckGo token to an otherwise standard Safari string\", { gpc: true }),\n\n // ---------------------------------------------------------------------------\n // Chromium mobile\n // ---------------------------------------------------------------------------\n landing(\"chromeAndroid\", \"Chrome 150 on an Android phone\", \"The device string has been frozen to 'Android 10; K' since Chrome 110 to reduce passive fingerprinting\"),\n landing(\n \"chromeAndroidTablet\",\n \"Chrome 150 on an Android tablet\",\n \"Chromium omits the Mobile product token on tablets and sets Sec-CH-UA-Mobile to ?0 from the same internal state\",\n {},\n \"The case that caught a real bug: a check testing the User-Agent for the operating system rather than for the Mobile token read this correct pairing as a contradiction, on every Android tablet on the web.\",\n ),\n landing(\"samsungInternet\", \"Samsung Internet 29 on a Galaxy S25\", \"The default browser on Samsung devices and the second most-used mobile browser worldwide\"),\n landing(\"operaAndroid\", \"Opera 91 on Android, Indonesian locale\", \"Opera has a large share across Southeast Asia\"),\n landing(\"edgeAndroid\", \"Edge 150 on Android\", \"EdgA is genuine Chromium and does send Client Hints\"),\n\n // ---------------------------------------------------------------------------\n // Regional Chromium forks. Hundreds of millions of people, pinned to older\n // Chromium releases and not sending the modern header set.\n // ---------------------------------------------------------------------------\n landing(\n \"ucBrowser\",\n \"UC Browser on Android in India\",\n \"UC Browser runs a Chromium fork pinned well behind the release channel and does not send Client Hints or Fetch Metadata\",\n {},\n \"A client claiming Chrome 100 with none of the headers Chrome 100 sends. Read literally that is a strong impersonation signal; read correctly it is one of the most-used browsers in South Asia.\",\n ),\n landing(\"miBrowser\", \"MIUI Browser on a Xiaomi phone\", \"The default browser on Xiaomi devices, based on an older Chromium and shipping a reduced header set\"),\n landing(\"huaweiBrowser\", \"Huawei Browser on an HMS device\", \"The default browser on Huawei devices outside Google Mobile Services\"),\n landing(\"qqBrowser\", \"QQ Browser on Android in China\", \"One of the most-used mobile browsers in China; a Chromium fork with its own release cadence\"),\n\n // ---------------------------------------------------------------------------\n // Televisions, consoles and embedded. Old engines, odd header sets, real people.\n // ---------------------------------------------------------------------------\n landing(\"silkKindle\", \"Amazon Silk on a Fire tablet\", \"Silk identifies itself as 'like Chrome' rather than as Chrome, and is a Chromium fork on Amazon's own cadence\"),\n landing(\"tizenTv\", \"A Samsung television browser\", \"Tizen builds report SMART-TV and a Chromium version years behind the desktop channel\"),\n landing(\"webOsTv\", \"An LG television browser\", \"webOS builds append WebAppManager and carry an old Chromium\"),\n landing(\"playstation\", \"A PlayStation 5 system browser\", \"WebKit-based, with a frozen Version/16.0 and no Fetch Metadata\"),\n landing(\"nintendoSwitch\", \"A Nintendo Switch browser\", \"The Switch's captive-portal browser, an old WebKit with a distinctive NintendoBrowser token\"),\n\n // ---------------------------------------------------------------------------\n // The same clients doing the things people actually do with them.\n // ---------------------------------------------------------------------------\n ...scenarios(\"chromeWindows\", \"Chrome on Windows\", COMMON),\n ...scenarios(\"firefoxWindows\", \"Firefox on Windows\", COMMON),\n ...scenarios(\"safariMac\", \"Safari on macOS\", COMMON),\n ...scenarios(\"edgeWindows\", \"Edge on Windows\", COMMON),\n ...scenarios(\"safariIos\", \"Safari on iPhone\", [...COMMON, ...MOBILE_EXTRAS]),\n ...scenarios(\"chromeAndroid\", \"Chrome on Android\", [...COMMON, ...MOBILE_EXTRAS]),\n ...scenarios(\"samsungInternet\", \"Samsung Internet\", MOBILE_EXTRAS),\n ...scenarios(\"chromeMac\", \"Chrome on macOS\", COMMON.slice(0, 3)),\n ...scenarios(\"firefoxAndroid\", \"Firefox on Android\", MOBILE_EXTRAS),\n\n // ---------------------------------------------------------------------------\n // The awkward edges of ordinary browsing.\n // ---------------------------------------------------------------------------\n human({\n id: \"browse-early-data-resumption\",\n title: \"A TLS 1.3 resumption replayed as early data\",\n category: \"browser-scenario\",\n provenance: \"0-RTT resumption; the terminating proxy marks the request Early-Data: 1 so the origin can decide whether to risk replaying it\",\n requests: [{ ...browser(\"chromeWindows\", { cookie: returningCustomerJar(\"early\"), earlyData: true, kind: \"same-origin-navigate\", referer: `${SITE}/` }), path: \"/products\" }],\n expect: { certain: false },\n }),\n human({\n id: \"browse-third-party-iframe\",\n title: \"A page embedded in a third-party iframe\",\n category: \"browser-scenario\",\n provenance: \"Cross-site iframe load; recent Chromium adds Sec-Fetch-Storage-Access to describe its storage partition\",\n requests: [{ ...browser(\"chromeWindows\", { kind: \"iframe\", referer: \"https://partner.example/\", storageAccess: \"none\" }), path: \"/embed/widget\" }],\n expect: { certain: false },\n }),\n human({\n id: \"browse-video-range-request\",\n title: \"A video player asking for a byte range\",\n category: \"browser-scenario\",\n provenance: \"HTML media elements issue Range requests with Sec-Fetch-Dest: video\",\n requests: [{ ...browser(\"safariMac\", { kind: \"media\", range: \"bytes=2097152-4194303\", referer: `${SITE}/products/1184` }), path: \"/media/demo.mp4\" }],\n expect: { certain: false },\n }),\n human({\n id: \"browse-eventsource-stream\",\n title: \"An EventSource stream for live stock updates\",\n category: \"browser-scenario\",\n provenance: \"EventSource sends Accept: text/event-stream and holds the connection open\",\n requests: [{ ...browser(\"chromeWindows\", { kind: \"eventsource\", cookie: returningCustomerJar(\"sse\"), origin: SITE }), path: \"/api/stock-stream\" }],\n expect: { certain: false },\n }),\n human({\n id: \"browse-cross-origin-cors\",\n title: \"A cross-origin API call from a partner's page\",\n category: \"browser-scenario\",\n provenance: \"Cross-site fetch with an Origin header and Sec-Fetch-Site: cross-site\",\n requests: [{ ...browser(\"chromeWindows\", { kind: \"cors\", origin: \"https://partner.example\" }), path: \"/api/public/catalog\" }],\n expect: { certain: false },\n }),\n human({\n id: \"browse-stylesheet-and-script\",\n title: \"The subresources of a page load\",\n category: \"browser-scenario\",\n provenance: \"Stylesheet and script requests carry their own Accept values and Sec-Fetch-Dest\",\n requests: [\n { ...browser(\"chromeWindows\", { kind: \"stylesheet\", referer: `${SITE}/` }), path: \"/assets/app.css\", atMs: 0 },\n { ...browser(\"chromeWindows\", { kind: \"script\", referer: `${SITE}/` }), path: \"/assets/app.js\", atMs: 40 },\n { ...browser(\"chromeWindows\", { kind: \"subresource\", referer: `${SITE}/` }), path: \"/assets/hero.avif\", atMs: 90 },\n ],\n expect: { certain: false },\n }),\n human({\n id: \"browse-dark-mode-reduced-motion\",\n title: \"Somebody who prefers dark mode and reduced motion\",\n category: \"browser-scenario\",\n provenance: \"Sec-CH-Prefers-Color-Scheme and Sec-CH-Prefers-Reduced-Motion, sent once a server advertises Accept-CH\",\n notes: \"Reduced motion is frequently an accessibility setting rather than a taste. A signal that read unusual preferences as suspicious would land hardest on the people least able to work around it.\",\n requests: [{ ...browser(\"chromeWindows\", { cookie: returningCustomerJar(\"prefs\"), prefers: { colorScheme: \"dark\", reducedMotion: \"reduce\" }, highEntropyHints: true }), path: \"/\" }],\n expect: { certain: false },\n tags: [\"accessibility\"],\n }),\n human({\n id: \"browse-layout-hints\",\n title: \"A responsive image request carrying layout hints\",\n category: \"browser-scenario\",\n provenance: \"Viewport-Width and DPR, sent when the server asks for them so it can pick an image size\",\n requests: [{ ...browser(\"chromeAndroid\", { kind: \"subresource\", layoutHints: { viewportWidth: 412, dpr: 2.625 }, referer: `${SITE}/products/1184` }), path: \"/img/1184.avif\" }],\n expect: { certain: false },\n }),\n human({\n id: \"browse-do-not-track\",\n title: \"Somebody sending the deprecated DNT header\",\n category: \"browser-scenario\",\n provenance: \"DNT is deprecated and still sent by a meaningful minority, often alongside Sec-GPC\",\n requests: [{ ...browser(\"firefoxWindows\", { dnt: true, gpc: true, cookie: freshVisitorJar(\"dnt\") }), path: \"/\" }],\n expect: { certain: false, verdict: \"unknown\" },\n }),\n];\n","import { plain } from \"./headers.js\";\nimport { cookieJar } from \"./cookies.js\";\nimport { human } from \"./schema.js\";\nimport type { Header } from \"./headers.js\";\nimport type { TrafficCase } from \"./schema.js\";\n\n/**\n * People inside somebody else's app.\n *\n * A large and growing share of mobile traffic never touches a standalone browser. A\n * link tapped in Instagram, TikTok, Discord or a banking app opens in an embedded\n * WebView: a real engine, a real person, and a User-Agent carrying the host\n * application's name bolted onto the end.\n *\n * These are the clients most likely to be misread, for a specific reason. An embedded\n * WebView is frequently pinned to an older Chromium and frequently omits the Client\n * Hints and Fetch Metadata that a standalone browser of the claimed version would\n * send. Read literally that is a strong impersonation signal. Read correctly it is\n * a customer who tapped a link in an app.\n *\n * The desktop half of the file is the same problem in a different shape: Electron\n * applications embed a real Chromium with a person driving it, and one of them —\n * VS Code's Simple Browser — was misclassified as proven automation by a shipped\n * version of this library.\n */\n\n/** An in-app WebView: an engine string, an app suffix, and the reduced header set they send. */\nfunction webview(id: string, title: string, userAgent: string, provenance: string, options: { notes?: string; extra?: readonly Header[]; language?: string; cookie?: boolean } = {}): TrafficCase {\n const headers: Header[] = [\n [\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\"],\n [\"Accept-Language\", options.language ?? \"en-GB,en;q=0.9\"],\n [\"Accept-Encoding\", \"gzip, deflate, br\"],\n [\"Upgrade-Insecure-Requests\", \"1\"],\n ...(options.extra ?? []),\n ...(options.cookie === false ? [] : ([[\"Cookie\", cookieJar({ visitor: id, analytics: true })]] as Header[])),\n ];\n return human({\n id,\n title,\n category: \"in-app-webview\",\n provenance,\n ...(options.notes !== undefined ? { notes: options.notes } : {}),\n requests: [plain(userAgent, headers)],\n expect: { certain: false },\n });\n}\n\nexport const HUMAN_APP_CASES: TrafficCase[] = [\n // ---------------------------------------------------------------------------\n // Social apps on iOS. WebKit underneath, with the app's own suffix.\n // ---------------------------------------------------------------------------\n webview(\n \"app-instagram-ios\",\n \"A link tapped in Instagram on iOS\",\n \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Instagram 372.1.0.28.104 (iPhone17,1; iOS 18_6_1; en_GB; en; scale=3.00; 1206x2622; 748291056; IABMV/1)\",\n \"Instagram appends its version plus a full device descriptor and drops the Safari token entirely\",\n { notes: \"Note what is missing: no Safari/ token, so a check keyed on the browser name finds nothing to check. The IABMV flag marks the in-app browser build.\" },\n ),\n webview(\"app-instagram-android\", \"A link tapped in Instagram on Android\", \"Mozilla/5.0 (Linux; Android 15; SM-S931B Build/AP3A.240905.015.A2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/149.0.7202.61 Mobile Safari/537.36 Instagram 372.0.0.39.108 Android (35/15; 450dpi; 1080x2229; samsung; SM-S931B; e3q; qcom; en_GB; 745123098)\", \"The Android build carries a wv token marking the WebView, plus a device and locale block\"),\n webview(\"app-facebook-ios\", \"A link tapped in Facebook on iOS\", \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 [FBAN/FBIOS;FBAV/500.0.0.44.107;FBBV/687451209;FBDV/iPhone17,1;FBMD/iPhone;FBSN/iOS;FBSV/18.6.1;FBSS/3;FBID/phone;FBLC/en_GB;FBOP/5;FBRV/0]\", \"The bracketed FB block names the app, build, device, OS and locale\"),\n webview(\"app-facebook-android\", \"A link tapped in Facebook on Android\", \"Mozilla/5.0 (Linux; Android 14; SM-A556B Build/UP1A.231005.007; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/147.0.7071.53 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/500.0.0.32.109;]\", \"Android Facebook WebViews use FB_IAB and FBAV rather than the iOS block\"),\n webview(\"app-messenger\", \"A link tapped in Messenger\", \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 [FBAN/MessengerForiOS;FBAV/500.1.0.52.106;FBBV/687912344;FBDV/iPhone16,2;FBMD/iPhone;FBSN/iOS;FBSV/18.6;FBSS/3;FBID/phone;FBLC/en_US;FBOP/5]\", \"Messenger identifies itself distinctly from the main Facebook app\"),\n webview(\"app-tiktok-android\", \"A link tapped in TikTok\", \"Mozilla/5.0 (Linux; Android 14; 23021RAA2Y Build/UKQ1.230917.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/143.0.6980.113 Mobile Safari/537.36 musical_ly_2024505030 JsSdk/1.0 NetType/WIFI Channel/googleplay AppName/musical_ly app_version/40.5.3 ByteLocale/en ByteFullLocale/en Region/GB AppSkin/white AppTheme/light BytedanceWebview/d8a21c6\", \"TikTok carries both the legacy musical_ly token and a BytedanceWebview build hash\", { notes: \"Contains 'Bytedance' but not 'Bytespider'. A substring match on the vendor name would classify a person as ByteDance's crawler.\" }),\n webview(\"app-tiktok-ios\", \"A link tapped in TikTok on iOS\", \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 musical_ly_40.5.3 JsSdk/2.0 NetType/WIFI Channel/App Store ByteLocale/en Region/US isDarkMode/0 WKWebView/1 BytedanceWebview/d8a21c6\", \"The iOS build names WKWebView explicitly\"),\n webview(\"app-snapchat\", \"A link tapped in Snapchat\", \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Snapchat/13.31.0.48 (like Safari/605.1.15)\", \"Snapchat's suffix says 'like Safari' rather than claiming to be Safari\"),\n webview(\"app-linkedin\", \"A link tapped in the LinkedIn app\", \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 [LinkedInApp]\", \"A bracketed suffix and nothing else\", { notes: \"Distinct from LinkedInBot, which is the unfurler. One is a person; the other is not; the strings differ by six characters.\" }),\n webview(\"app-pinterest\", \"A link tapped in Pinterest\", \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Pinterest for iOS/13.12\", \"Pinterest names itself and the platform in plain words\"),\n webview(\"app-reddit\", \"A link tapped in the Reddit app\", \"Mozilla/5.0 (Linux; Android 15; Pixel 9 Build/AP4A.250105.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/149.0.7202.61 Mobile Safari/537.36 RedditAndroid/2025.03.0\", \"The Reddit app opens links in a WebView by default\"),\n webview(\"app-x-twitter\", \"A link tapped in X\", \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Twitter for iPhone/10.71\", \"X still identifies its iOS client by the old product name\"),\n webview(\"app-discord\", \"A link tapped in Discord on mobile\", \"Mozilla/5.0 (Linux; Android 14; SM-G991B Build/UP1A.231005.007; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/147.0.7071.53 Mobile Safari/537.36 Discord/271.0\", \"Discord's mobile WebView\", { notes: \"Distinct from Discordbot, which unfurls the link into an embed before anyone taps it. Both arrive for the same shared URL, seconds apart.\" }),\n webview(\"app-telegram\", \"A link tapped in Telegram\", \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Telegram-iOS/11.5\", \"Telegram's in-app browser\"),\n webview(\"app-whatsapp\", \"A link tapped in WhatsApp\", \"Mozilla/5.0 (Linux; Android 15; SM-S931B Build/AP3A.240905.015; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/149.0.7202.61 Mobile Safari/537.36\", \"WhatsApp's Android WebView adds no suffix at all — only the wv token distinguishes it\", { notes: \"No app name anywhere. Indistinguishable from any other Android WebView, which is why the wv marker has to carry the weight.\" }),\n webview(\"app-line\", \"A link tapped in LINE\", \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Safari/604.1 Line/14.20.0\", \"LINE is the dominant messenger in Japan, Taiwan and Thailand\", { language: \"ja-JP,ja;q=0.9,en-US;q=0.8\" }),\n webview(\"app-kakaotalk\", \"A link tapped in KakaoTalk\", \"Mozilla/5.0 (Linux; Android 15; SM-S938N Build/AP3A.240905.015; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/147.0.7071.53 Mobile Safari/537.36 KAKAOTALK 25.3.1\", \"The dominant messenger in South Korea\", { language: \"ko-KR,ko;q=0.9,en-US;q=0.8\" }),\n webview(\"app-wechat-ios\", \"A link tapped in WeChat\", \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.58(0x18003a2b) NetType/WIFI Language/en\", \"WeChat's WebView is the entry point to an entire application ecosystem in China\", { language: \"zh-CN,zh;q=0.9,en;q=0.8\" }),\n webview(\"app-weibo\", \"A link tapped in Weibo\", \"Mozilla/5.0 (Linux; Android 14; 2211133C Build/UKQ1.230804.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/122.0.6261.119 Mobile Safari/537.36 Weibo (Xiaomi-2211133C__weibo__14.9.0__android__android14)\", \"Weibo packs the manufacturer, app and OS versions into a parenthesised block\", { language: \"zh-CN,zh;q=0.9\" }),\n webview(\"app-vk\", \"A link tapped in VK\", \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 VKClient/8.32\", \"The dominant social network across the CIS\", { language: \"ru-RU,ru;q=0.9,en-US;q=0.8\" }),\n\n // ---------------------------------------------------------------------------\n // Non-social apps that embed a browser: banking, travel, retail, news.\n // ---------------------------------------------------------------------------\n webview(\"app-banking-3ds\", \"A 3-D Secure challenge inside a banking app\", \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 ExampleBank/7.14.2\", \"Card authentication opens the issuer's page inside the app's WebView\", { notes: \"A payment failing here is a lost order and a support call, and the person has no way to switch browser.\" }),\n webview(\"app-retail-loyalty\", \"A retailer's app opening its own web page\", \"Mozilla/5.0 (Linux; Android 15; Pixel 8 Build/AP4A.250105.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/149.0.7202.61 Mobile Safari/537.36 ExampleShop/9.4.1\", \"Native apps commonly render loyalty and account pages as embedded web views\"),\n webview(\"app-travel-booking\", \"An airline app rendering a booking page\", \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 ExampleAir/6.2.0 (iOS)\", \"Booking flows are frequently web pages inside a native shell\"),\n webview(\"app-news-reader\", \"A news app opening a linked article\", \"Mozilla/5.0 (Linux; Android 14; moto g84 5G Build/U1TNS34.82-12-9; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/145.0.7049.100 Mobile Safari/537.36 ExampleNews/8.1.0\", \"News aggregators open the publisher's page in a WebView\"),\n human({\n id: \"app-podcast-shownotes\",\n title: \"A podcast app opening show notes\",\n category: \"in-app-webview\",\n provenance: \"The same application that fetches your feed also opens your links, with the same User-Agent\",\n notes:\n \"The most instructive case in this file, and the corpus caught the expectation being wrong before the library was. Overcast's User-Agent carries the crawler contact convention because the app also fetches feeds — so the library reads a *proven declared bot*, and it is right: that is genuinely what the client software is. There is a person behind this particular request and nothing in it says so. The verdict is about the client, not the intent, and the gap between those two is exactly why a proven verdict tags rather than blocks here.\",\n requests: [\n plain(\"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Overcast/2025.4 (+http://overcast.fm/; iOS podcast app)\", [\n [\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\"],\n [\"Accept-Language\", \"en-GB,en;q=0.9\"],\n [\"Accept-Encoding\", \"gzip, deflate, br\"],\n ]),\n ],\n selfDeclared:\n \"Overcast sends one User-Agent for both jobs, and it carries the crawler contact convention because the app also fetches feeds. The library reads a proven declaration and is correct about the client; there is simply a person behind this particular request and nothing in it says so.\",\n expect: { verdict: \"confirmed-bot\", certain: true, botClass: \"declared-bot\" },\n tags: [\"known-cost\", \"known-limit\"],\n }),\n human({\n id: \"app-kindle-in-book-link\",\n title: \"Following a footnote link from a Kindle book\",\n category: \"in-app-webview\",\n provenance: \"Kindle devices open external links in an embedded Silk-derived browser with a very reduced header set\",\n requests: [\n plain(\"Mozilla/5.0 (Linux; U; Android 11; en-GB; KFTRWI) AppleWebKit/537.36 (KHTML, like Gecko) Silk/128.1.2 like Chrome/128.0.6613.146 Safari/537.36\", [\n [\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"],\n [\"Accept-Language\", \"en-GB\"],\n [\"Accept-Encoding\", \"gzip, deflate\"],\n ]),\n ],\n expect: { certain: false },\n }),\n human({\n id: \"app-car-infotainment\",\n title: \"A passenger opening a link on a car's infotainment screen\",\n category: \"in-app-webview\",\n provenance: \"Android Automotive builds ship a WebView on an old Chromium with an unusual device string\",\n notes: \"A small population that no signature list will ever cover, on an engine years behind the release channel. The right treatment is the same as for any unfamiliar client: score it, do not deny it.\",\n requests: [\n plain(\"Mozilla/5.0 (Linux; Android 13; Automotive Build/TQ3A.230805.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/119.0.6045.193 Safari/537.36\", [\n [\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\"],\n [\"Accept-Language\", \"de-DE,de;q=0.9,en;q=0.8\"],\n [\"Accept-Encoding\", \"gzip, deflate, br\"],\n ]),\n ],\n expect: { certain: false },\n }),\n webview(\"app-email-client\", \"A mobile mail client opening a newsletter link\", \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Outlook-iOS/4.2531.0\", \"Outlook opens links in its own WebView rather than handing off to Safari\"),\n webview(\"app-google-app\", \"A link tapped in the Google app\", \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 GSA/354.0.723598878 Mobile/15E148 Safari/604.1\", \"GSA is the Google Search App's in-app browser and carries a very large share of mobile search traffic\"),\n webview(\"app-android-webview-bare\", \"A bare Android WebView from an unnamed app\", \"Mozilla/5.0 (Linux; Android 13; SM-A135F Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/140.0.7028.61 Mobile Safari/537.36\", \"Any application can embed a WebView and add nothing; only the wv token remains\", { cookie: false, notes: \"No app name, no cookies, an old Chromium and none of the modern headers. Close to the worst case a real person can present, and still a real person.\" }),\n webview(\"app-huawei-quick\", \"A Huawei Quick App\", \"Mozilla/5.0 (Linux; Android 12; ELS-NX9; HMSCore 6.14.0.302; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/126.0.6478.186 Mobile Safari/537.36 QuickApp/12.0.6\", \"Huawei's lightweight app format renders web content in a WebView\", { language: \"zh-CN,zh;q=0.9\" }),\n\n // ---------------------------------------------------------------------------\n // Desktop applications embedding a browser engine.\n // ---------------------------------------------------------------------------\n webview(\n \"app-vscode-simple-browser\",\n \"A developer opening a page in VS Code's Simple Browser\",\n \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.97.2 Chrome/132.0.6834.196 Electron/34.2.0 Safari/537.36\",\n \"The Electron User-Agent emitted by VS Code webviews\",\n { notes: \"This exact string was classified as proven automation by a shipped version of this library, because Electron sat in the headless signature set. It then looped on the challenge, because passing one cannot undo a proven verdict. Two bugs, one User-Agent, both found by a person opening the project's own demo.\" },\n ),\n webview(\"app-slack-desktop\", \"Slack's desktop client opening a link internally\", \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Slack/4.45.69 Chrome/134.0.6998.205 Electron/35.7.5 Safari/537.36\", \"Electron-based desktop client\"),\n webview(\"app-discord-desktop\", \"Discord's desktop client\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) discord/1.0.9200 Chrome/128.0.6613.186 Electron/32.2.7 Safari/537.36\", \"Electron again; the app name is lowercase here\"),\n webview(\"app-spotify-desktop\", \"Spotify's desktop client opening a link\", \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Spotify/1.2.62 Chrome/134.0.6998.205 Electron/35.7.5 Safari/537.36\", \"A person listening, not the podcast fetcher\", { notes: \"The reason no signature claims the token `Spotify/`: this is a listener, and `Spotify/1.0` is a feed fetcher. One prefix, two entirely different clients.\" }),\n webview(\"app-notion-desktop\", \"Notion's desktop client\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Notion/4.5.0 Chrome/128.0.6613.186 Electron/32.2.6 Safari/537.36\", \"Electron-based note application embedding pages\"),\n webview(\"app-figma-desktop\", \"Figma's desktop client loading an embed\", \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Figma/125.4.4 Chrome/126.0.6478.234 Electron/31.7.5 Safari/537.36\", \"Design tools embed live web previews\"),\n webview(\"app-postman-desktop\", \"Postman's desktop client rendering a documentation page\", \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Postman/11.30.0 Chrome/128.0.6613.186 Electron/32.2.6 Safari/537.36\", \"Distinct from PostmanRuntime, which is the request sender\", { notes: \"PostmanRuntime is automation; Postman the application is a person reading documentation. The two arrive from the same machine within seconds of each other.\" }),\n webview(\"app-steam-overlay\", \"The Steam in-game browser\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.234 Safari/537.36 Valve Steam GameOverlay/1740000000\", \"Steam's overlay browser opens links without leaving a game\"),\n webview(\"app-office-webview\", \"A link opened from Microsoft Word\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.186 Safari/537.36 Microsoft Office Word/16.0.18324\", \"Office applications open links through an embedded browser and announce the host application\"),\n webview(\"app-teams-desktop\", \"Microsoft Teams' desktop client\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.186 Electron/32.2.6 Safari/537.36 Teams/25.31.0\", \"Teams renders tabs and link previews in an embedded browser\"),\n];\n","import { browser, plain, userAgentOf } from \"./headers.js\";\nimport { DENYING_ACTIONS, human, humanPaced } from \"./schema.js\";\nimport type { CaseRequest, TrafficCase } from \"./schema.js\";\n\n/**\n * People.\n *\n * Every case here is a person at a keyboard or a phone, and every one of them is a\n * false positive if the library denies it service. That guarantee is enforced for the\n * whole file by {@link human}, which attaches `neverAction: [\"block\",\"drop\",\"redirect\"]`\n * to each case so it cannot be forgotten.\n *\n * The file is deliberately unflattering. Roughly a third of it is people whose setup\n * genuinely does look automated — privacy browsers, stripped headers, cookie\n * blocking, corporate proxies, ten-year-old devices — and their expectations record\n * what the library *actually* does to them rather than what we would like it to do.\n * Where the honest answer is \"this person gets challenged\", the case says so. That is\n * the cost of the policy, written down where it can be argued about, instead of\n * hidden behind an average.\n */\n\n/**\n * Inserts the revalidation headers where Chrome actually puts them — straight after\n * `Connection`, ahead of the Client Hints block.\n */\nfunction withCacheValidators(request: CaseRequest): CaseRequest {\n const headers = [...request.headers];\n headers.splice(2, 0, [\"Cache-Control\", \"max-age=0\"], [\"If-None-Match\", '\"a1b2c3d4\"']);\n return { ...request, headers };\n}\n\nconst CLEAN: readonly string[] = [\n \"self-identified\",\n \"header-integrity\",\n \"client-hints\",\n \"fetch-metadata\",\n \"accept-signature\",\n \"header-order\",\n \"trap\",\n \"ip-intelligence\",\n];\n\n/** A person on a current, unmodified browser. Nothing at all should fire. */\nfunction pristine(id: string, title: string, request: ReturnType<typeof browser>, provenance: string): TrafficCase {\n return human({\n id,\n title,\n category: \"mainstream-browser\",\n provenance,\n requests: [request],\n expect: { verdict: \"unknown\", maxScore: 0, notDetectors: CLEAN, action: \"allow\" },\n });\n}\n\nexport const HUMAN_CASES: TrafficCase[] = [\n // ---------------------------------------------------------------------------\n // Applications that put their own address in the User-Agent.\n //\n // A great many do — native apps with a webview, desktop clients, anything whose\n // author wanted a server operator to be able to reach them. None of it says the\n // request is automated, and the person behind it is an ordinary customer. The\n // `self-identified` detector used to read a bare URL or email as the whole of a\n // self-declaration and reach `certain` on it, which is a proven bot verdict on a\n // person and the one thing this corpus exists to make impossible.\n // ---------------------------------------------------------------------------\n human({\n id: \"app-webview-support-email\",\n title: \"A banking app's webview, naming its support address\",\n category: \"in-app-browser\",\n provenance: \"Native apps commonly append a product token and a contact to the system webview's User-Agent\",\n notes: \"Contains an email address and no crawler word anywhere. Believing a client's declaration is safe; inventing one for it is not.\",\n requests: [\n plain(\n \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36 Notes/3.1 (support@notes.example)\",\n ),\n ],\n expect: { certain: false, neverAction: DENYING_ACTIONS },\n }),\n\n human({\n id: \"desktop-client-homepage-url\",\n title: \"A desktop client naming its homepage\",\n category: \"in-app-browser\",\n provenance: \"A bare https:// URL in a User-Agent, without the `+` crawler convention and without a crawler word\",\n requests: [\n plain(\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.7 Safari/605.1.15 Ledger/2.4 (https://ledger.example)\",\n ),\n ],\n expect: { certain: false, neverAction: DENYING_ACTIONS },\n }),\n\n // ---------------------------------------------------------------------------\n // Mainstream desktop. If any of these ever fails, stop and fix it before\n // anything else in the library.\n // ---------------------------------------------------------------------------\n pristine(\"chrome-windows\", \"Chrome 152 on Windows 11\", browser(\"chromeWindows\"), \"2026 User-Agent lists; Chromium freezes the minor version to 0.0.0\"),\n pristine(\"chrome-macos\", \"Chrome 152 on macOS\", browser(\"chromeMac\"), \"2026 User-Agent lists\"),\n pristine(\"chrome-linux\", \"Chrome 152 on Linux\", browser(\"chromeLinux\"), \"2026 User-Agent lists\"),\n pristine(\"edge-windows\", \"Edge 150 on Windows\", browser(\"edgeWindows\"), \"Edge reports Chromium brands alongside its own; UA carries Edg/\"),\n pristine(\"firefox-windows\", \"Firefox 148 on Windows\", browser(\"firefoxWindows\"), \"2026 User-Agent lists; Gecko sends no Client Hints\"),\n pristine(\"firefox-linux\", \"Firefox 148 on Linux, German locale\", browser(\"firefoxLinux\"), \"2026 User-Agent lists\"),\n pristine(\"safari-macos\", \"Safari 18.7 on macOS\", browser(\"safariMac\"), \"WebKit interleaves Sec-Fetch-* with content negotiation\"),\n\n // ---------------------------------------------------------------------------\n // Mainstream mobile. More than half the web.\n // ---------------------------------------------------------------------------\n pristine(\"safari-ios\", \"Safari 18.7 on iPhone\", browser(\"safariIos\"), \"2026 User-Agent lists\"),\n pristine(\"chrome-android\", \"Chrome 150 on Android\", browser(\"chromeAndroid\"), \"Android UA frozen to 'Android 10; K' since Chrome 110\"),\n pristine(\"samsung-internet\", \"Samsung Internet 27 on a Galaxy S24\", browser(\"samsungInternet\"), \"Chromium fork with its own product token; very common in Korea and India\"),\n\n human({\n id: \"chrome-android-cross-site-arrival\",\n title: \"Arriving on Android from a Google search result\",\n category: \"mainstream-browser\",\n provenance: \"Fetch Metadata spec: a cross-origin navigation reports Sec-Fetch-Site: cross-site\",\n requests: [browser(\"chromeAndroid\", { kind: \"cross-site-navigate\", referer: \"https://www.google.com/\" })],\n expect: { verdict: \"unknown\", maxScore: 0, action: \"allow\" },\n }),\n\n human({\n id: \"safari-ios-xhr\",\n title: \"An in-page fetch() for JSON from iOS Safari\",\n category: \"mainstream-browser\",\n provenance: \"Fetch Metadata spec: same-origin cors/empty\",\n notes: \"Accept is */* here, and that is correct for fetch(). The accept-signature detector must not read it as a navigation.\",\n requests: [{ ...browser(\"safariIos\", { kind: \"xhr\" }), path: \"/api/cart\" }],\n expect: { verdict: \"unknown\", maxScore: 0, notDetectors: [\"accept-signature\"], action: \"allow\" },\n }),\n\n human({\n id: \"chrome-subresource-image\",\n title: \"Chrome loading an image on the page\",\n category: \"mainstream-browser\",\n provenance: \"Fetch Metadata spec: no-cors/image\",\n requests: [browser(\"chromeWindows\", { kind: \"subresource\" })],\n expect: { verdict: \"unknown\", maxScore: 0, action: \"allow\" },\n }),\n\n // ---------------------------------------------------------------------------\n // In-app browsers. A large and growing share of mobile traffic, and the group\n // most likely to be misread: the UA is a real engine wearing an app's badge.\n // ---------------------------------------------------------------------------\n human({\n id: \"instagram-webview-ios\",\n title: \"Tapping a link in Instagram on iOS\",\n category: \"in-app-webview\",\n provenance: \"Instagram appends an 'Instagram <version>' token plus device metadata; no Safari/ token remains\",\n requests: [\n plain(\n \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Instagram 361.0.0.31.98 (iPhone16,2; iOS 18_5; en_US; en; scale=3.00; 1290x2796; 682468081)\",\n [[\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"], [\"Accept-Language\", \"en-US,en;q=0.9\"], [\"Accept-Encoding\", \"gzip, deflate, br\"]],\n ),\n ],\n expect: { verdict: \"unknown\", maxScore: 30, action: [\"allow\", \"tag\", \"log\"] },\n }),\n\n human({\n id: \"facebook-webview-android\",\n title: \"Tapping a link in Facebook on Android\",\n category: \"in-app-webview\",\n provenance: \"Android Facebook webviews carry FB_IAB and FBAV tokens\",\n requests: [\n plain(\n \"Mozilla/5.0 (Linux; Android 14; SM-A546B Build/UP1A.231005.007; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/149.0.0.0 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/491.0.0.42.63;]\",\n [[\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\"], [\"Accept-Language\", \"en-GB,en;q=0.9\"], [\"Accept-Encoding\", \"gzip, deflate, br\"], [\"Sec-Fetch-Site\", \"none\"], [\"Sec-Fetch-Mode\", \"navigate\"], [\"Sec-Fetch-Dest\", \"document\"]],\n ),\n ],\n expect: { verdict: [\"unknown\", \"suspected-bot\"], action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\"] },\n notes: \"An Android webview claims Chrome but sends no Sec-CH-UA, so it scores. It must never be denied.\",\n }),\n\n human({\n id: \"tiktok-webview\",\n title: \"Tapping a link in TikTok\",\n category: \"in-app-webview\",\n provenance: \"TikTok webviews carry musical_ly or BytedanceWebview tokens\",\n requests: [\n plain(\n \"Mozilla/5.0 (Linux; Android 13; V2145 Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/143.0.0.0 Mobile Safari/537.36 musical_ly_2023905040 JsSdk/1.0 NetType/WIFI Channel/googleplay AppName/musical_ly app_version/39.5.4 ByteLocale/en ByteFullLocale/en Region/GB BytedanceWebview/d8a21c6\",\n [[\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\"], [\"Accept-Language\", \"en-GB,en;q=0.9\"], [\"Accept-Encoding\", \"gzip, deflate\"]],\n ),\n ],\n expect: { verdict: [\"unknown\", \"suspected-bot\"], action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\"] },\n notes: \"Contains 'Bytedance' but NOT 'Bytespider'. A substring match on the vendor name here would block a person.\",\n }),\n\n human({\n id: \"snapchat-webview\",\n title: \"Tapping a link in Snapchat\",\n category: \"in-app-webview\",\n provenance: \"Snapchat webviews append a Snapchat token\",\n requests: [\n plain(\n \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Snapchat/13.24.0.44 (like Safari/605.1.15)\",\n [[\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"], [\"Accept-Language\", \"en-US,en;q=0.9\"], [\"Accept-Encoding\", \"gzip, deflate, br\"]],\n ),\n ],\n expect: { verdict: [\"unknown\", \"suspected-bot\"], action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\"] },\n }),\n\n human({\n id: \"linkedin-webview\",\n title: \"Tapping a link in the LinkedIn app\",\n category: \"in-app-webview\",\n provenance: \"LinkedIn webviews carry a LinkedInApp token\",\n notes: \"Distinct from LinkedInBot, which is the unfurler. One is a person; the other is not.\",\n requests: [\n plain(\n \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 [LinkedInApp]\",\n [[\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"], [\"Accept-Language\", \"en-US,en;q=0.9\"], [\"Accept-Encoding\", \"gzip, deflate, br\"]],\n ),\n ],\n expect: { verdict: [\"unknown\", \"suspected-bot\"], action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\"] },\n }),\n\n human({\n id: \"vscode-simple-browser\",\n title: \"A developer opening a page in VS Code's Simple Browser\",\n category: \"embedded-app\",\n provenance: \"Electron UA emitted by VS Code webviews. This exact case was a shipped bug: Electron sat in the headless signature set and produced a proven-automation verdict for a person, which then looped on the challenge.\",\n requests: [\n plain(\n \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.95.3 Chrome/128.0.6613.36 Electron/32.2.1 Safari/537.36\",\n [[\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\"], [\"Accept-Language\", \"en-US\"], [\"Accept-Encoding\", \"gzip, deflate, br\"], [\"Sec-Fetch-Site\", \"none\"], [\"Sec-Fetch-Mode\", \"navigate\"], [\"Sec-Fetch-Dest\", \"document\"]],\n ),\n ],\n expect: { certain: false, botClass: [\"unknown\", \"impersonator\"], action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\"] },\n tags: [\"regression\"],\n }),\n\n human({\n id: \"slack-desktop\",\n title: \"Slack's desktop app opening a link internally\",\n category: \"embedded-app\",\n provenance: \"Electron-based desktop client\",\n requests: [\n plain(\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Slack/4.44.65 Chrome/134.0.6998.205 Electron/35.7.5 Safari/537.36\",\n [[\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"], [\"Accept-Language\", \"en-GB\"], [\"Accept-Encoding\", \"gzip, deflate, br\"]],\n ),\n ],\n expect: { certain: false, action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\"] },\n }),\n\n // ---------------------------------------------------------------------------\n // Privacy-hardened clients. The population most likely to be misclassified, and\n // the one with the strongest reasons for its configuration.\n // ---------------------------------------------------------------------------\n human({\n id: \"tor-browser\",\n title: \"Tor Browser\",\n category: \"privacy-hardened\",\n provenance: \"Tor Browser ships one frozen Firefox UA for every user on every platform, and normalises Accept-Language to en-US,en;q=0.5\",\n notes: \"The whole design goal is that every Tor user looks identical. It is Firefox-shaped and complete, so it should assess cleanly.\",\n requests: [\n {\n headers: [\n [\"Host\", \"shop.example\"],\n [\"User-Agent\", \"Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0\"],\n [\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\"],\n [\"Accept-Language\", \"en-US,en;q=0.5\"],\n [\"Accept-Encoding\", \"gzip, deflate, br\"],\n [\"Connection\", \"keep-alive\"],\n [\"Upgrade-Insecure-Requests\", \"1\"],\n [\"Sec-Fetch-Dest\", \"document\"],\n [\"Sec-Fetch-Mode\", \"navigate\"],\n [\"Sec-Fetch-Site\", \"none\"],\n [\"Sec-Fetch-User\", \"?1\"],\n ],\n protocol: \"https\",\n httpVersion: \"1.1\",\n },\n ],\n expect: { verdict: \"unknown\", maxScore: 0, action: \"allow\" },\n }),\n\n human({\n id: \"firefox-resist-fingerprinting\",\n title: \"Firefox with privacy.resistFingerprinting enabled\",\n category: \"privacy-hardened\",\n provenance: \"resistFingerprinting freezes the UA to a generic Windows Firefox ESR and pins Accept-Language to en-US\",\n requests: [\n {\n headers: [\n [\"Host\", \"shop.example\"],\n [\"User-Agent\", \"Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0\"],\n [\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"],\n [\"Accept-Language\", \"en-US, en\"],\n [\"Accept-Encoding\", \"gzip, deflate, br\"],\n [\"Connection\", \"keep-alive\"],\n [\"Upgrade-Insecure-Requests\", \"1\"],\n [\"Sec-Fetch-Dest\", \"document\"],\n [\"Sec-Fetch-Mode\", \"navigate\"],\n [\"Sec-Fetch-Site\", \"none\"],\n [\"Sec-Fetch-User\", \"?1\"],\n ],\n protocol: \"https\",\n httpVersion: \"1.1\",\n },\n ],\n expect: { verdict: \"unknown\", maxScore: 20, action: [\"allow\", \"tag\", \"log\"] },\n }),\n\n human({\n id: \"brave-shields\",\n title: \"Brave with Shields up\",\n category: \"privacy-hardened\",\n provenance: \"Brave presents an unmodified Chrome UA by design and does send Client Hints\",\n requests: [browser(\"chromeWindows\", { acceptLanguage: \"en-US,en;q=0.9\" })],\n expect: { verdict: \"unknown\", maxScore: 0, action: \"allow\" },\n }),\n\n human({\n id: \"ua-spoofing-extension\",\n title: \"A person running a User-Agent spoofing extension\",\n category: \"privacy-hardened\",\n provenance: \"Extensions rewrite navigator.userAgent and the UA header but cannot rewrite Sec-CH-UA, so the two disagree\",\n notes:\n \"A real person, and the library will score them: this is the exact contradiction the client-hints detector looks for. It is why that detector is capped at `strong` and can never block. The corpus records the cost — a challenge — rather than pretending it is zero.\",\n requests: [\n {\n headers: [\n [\"Host\", \"shop.example\"],\n [\"Connection\", \"keep-alive\"],\n [\"sec-ch-ua\", '\"Not(A:Brand\";v=\"99\", \"Google Chrome\";v=\"152\", \"Chromium\";v=\"152\"'],\n [\"sec-ch-ua-mobile\", \"?0\"],\n [\"sec-ch-ua-platform\", '\"Windows\"'],\n [\"Upgrade-Insecure-Requests\", \"1\"],\n [\"User-Agent\", \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.7 Safari/605.1.15\"],\n [\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"],\n [\"Sec-Fetch-Site\", \"none\"],\n [\"Sec-Fetch-Mode\", \"navigate\"],\n [\"Sec-Fetch-User\", \"?1\"],\n [\"Sec-Fetch-Dest\", \"document\"],\n [\"Accept-Encoding\", \"gzip, deflate, br\"],\n [\"Accept-Language\", \"en-US,en;q=0.9\"],\n ],\n protocol: \"https\",\n httpVersion: \"1.1\",\n },\n ],\n expect: { certain: false, action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\", \"rate-limit\"] },\n tags: [\"known-cost\"],\n }),\n\n human({\n id: \"cookies-blocked\",\n title: \"Somebody who blocks all cookies, browsing at length\",\n category: \"privacy-hardened\",\n provenance: \"session-integrity reports an actor that never returns any cookie after a dozen requests\",\n notes: \"Blocking cookies is a legitimate choice made by real people. The detector is capped at moderate for exactly this case.\",\n requests: humanPaced(browser(\"chromeWindows\", { kind: \"same-origin-navigate\", referer: \"https://shop.example/\" }), [\n \"/\", \"/products\", \"/products/7\", \"/products/12\", \"/about\", \"/products/31\", \"/search?q=lamp\",\n \"/products/44\", \"/basket\", \"/products/9\", \"/delivery\", \"/products/18\", \"/contact\", \"/products/2\",\n ]),\n expect: { certain: false, action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\", \"rate-limit\"] },\n tags: [\"known-cost\"],\n }),\n\n // ---------------------------------------------------------------------------\n // Assistive technology and text clients. Small populations, high stakes: these\n // are people for whom an alternative route around a block usually does not exist.\n // ---------------------------------------------------------------------------\n human({\n id: \"lynx-text-browser\",\n title: \"Lynx, a text-mode browser\",\n category: \"assistive\",\n provenance: \"Lynx is used with refreshable braille displays and over slow links; it renders no JavaScript at all\",\n notes: \"A JavaScript challenge locks this person out permanently. That is the argument for contactHtml being mandatory.\",\n requests: [\n {\n headers: [\n [\"Host\", \"shop.example\"],\n [\"Accept\", \"text/html, text/plain, text/sgml, text/css, application/xhtml+xml, */*;q=0.01\"],\n [\"Accept-Encoding\", \"gzip, compress, bzip2\"],\n [\"Accept-Language\", \"en\"],\n [\"User-Agent\", \"Lynx/2.9.2 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/3.0.14\"],\n ],\n protocol: \"https\",\n httpVersion: \"1.1\",\n },\n ],\n expect: { certain: false, action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\", \"rate-limit\"] },\n tags: [\"known-cost\", \"accessibility\"],\n }),\n\n human({\n id: \"w3m-text-browser\",\n title: \"w3m, a text-mode browser\",\n category: \"assistive\",\n provenance: \"Common in terminal workflows and on low-bandwidth connections\",\n requests: [\n {\n headers: [\n [\"Host\", \"shop.example\"],\n [\"User-Agent\", \"w3m/0.5.3+git20230121\"],\n [\"Accept\", \"text/html, text/*;q=0.5, image/*, application/*, audio/*, */*;q=0.1\"],\n [\"Accept-Encoding\", \"gzip, deflate, br\"],\n [\"Accept-Language\", \"en;q=1.0\"],\n ],\n protocol: \"https\",\n httpVersion: \"1.1\",\n },\n ],\n expect: { certain: false, action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\", \"rate-limit\"] },\n tags: [\"known-cost\", \"accessibility\"],\n }),\n\n human({\n id: \"screen-reader-firefox\",\n title: \"NVDA driving Firefox\",\n category: \"assistive\",\n provenance: \"A screen reader reads the rendered page; the HTTP request is an ordinary Firefox request\",\n notes: \"There is no header that distinguishes this from any other Firefox. It is here to make that point explicit: assistive technology is invisible at the HTTP layer, and any heuristic claiming to spot it is wrong.\",\n requests: [browser(\"firefoxWindows\")],\n expect: { verdict: \"unknown\", maxScore: 0, action: \"allow\" },\n tags: [\"accessibility\"],\n }),\n\n // ---------------------------------------------------------------------------\n // Old and unusual devices. Disproportionately owned by people who cannot\n // simply buy a newer one.\n // ---------------------------------------------------------------------------\n human({\n id: \"internet-explorer-11\",\n title: \"Internet Explorer 11 on Windows 10\",\n category: \"legacy-client\",\n provenance: \"Still present in government, healthcare and industrial deployments\",\n requests: [\n {\n headers: [\n [\"Accept\", \"text/html, application/xhtml+xml, image/jxr, */*\"],\n [\"Accept-Language\", \"en-GB\"],\n [\"User-Agent\", \"Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko\"],\n [\"Accept-Encoding\", \"gzip, deflate\"],\n [\"Host\", \"shop.example\"],\n [\"Connection\", \"Keep-Alive\"],\n ],\n protocol: \"https\",\n httpVersion: \"1.1\",\n },\n ],\n expect: { certain: false, action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\", \"rate-limit\"] },\n notes: \"Note the header order: IE sends Host near the end, which trips the weakest header-order rule.\",\n tags: [\"known-cost\"],\n }),\n\n human({\n id: \"android-4-webview\",\n title: \"A ten-year-old Android tablet\",\n category: \"legacy-client\",\n provenance: \"Android 4.4 stock browser UA\",\n requests: [\n plain(\n \"Mozilla/5.0 (Linux; U; Android 4.4.2; en-gb; SM-T230 Build/KOT49H) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30\",\n [[\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"], [\"Accept-Language\", \"en-GB, en-US\"], [\"Accept-Encoding\", \"gzip, deflate\"]],\n ),\n ],\n expect: { certain: false, action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\", \"rate-limit\"] },\n }),\n\n human({\n id: \"smart-tv-browser\",\n title: \"A smart TV browser\",\n category: \"legacy-client\",\n provenance: \"Tizen browser on a Samsung television\",\n requests: [\n plain(\n \"Mozilla/5.0 (SMART-TV; LINUX; Tizen 7.0) AppleWebKit/537.36 (KHTML, like Gecko) 94.0.4606.31/7.0 TV Safari/537.36\",\n [[\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"], [\"Accept-Language\", \"en-US\"], [\"Accept-Encoding\", \"gzip, deflate\"]],\n ),\n ],\n expect: { certain: false, action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\", \"rate-limit\"] },\n }),\n\n human({\n id: \"playstation-browser\",\n title: \"A games console browser\",\n category: \"legacy-client\",\n provenance: \"PlayStation 5 system browser\",\n requests: [\n plain(\n \"Mozilla/5.0 (PlayStation; PlayStation 5/8.20) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15\",\n [[\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"], [\"Accept-Language\", \"en-GB\"], [\"Accept-Encoding\", \"gzip, deflate, br\"]],\n ),\n ],\n expect: { certain: false, action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\", \"rate-limit\"] },\n }),\n\n // ---------------------------------------------------------------------------\n // Normal browsing shapes. These exercise the behavioural detectors with traffic\n // that must not trigger them.\n // ---------------------------------------------------------------------------\n human({\n id: \"reading-session\",\n title: \"A person reading a shop at human pace\",\n category: \"behaviour\",\n provenance: \"Irregular gaps, repeat visits, a referer chain and a session cookie\",\n notes: \"The revisits matter: a person's distinct-path ratio stays well below one, which is what separates reading from enumerating.\",\n requests: humanPaced(browser(\"chromeWindows\", { kind: \"same-origin-navigate\", cookie: \"sid=a1b2c3; consent=1\", referer: \"https://shop.example/products\" }), [\n \"/\", \"/products\", \"/products/14\", \"/products\", \"/products/9\", \"/products/14\", \"/basket\", \"/products\", \"/products/9\", \"/checkout\",\n ]),\n expect: { verdict: \"unknown\", maxScore: 20, notDetectors: [\"cadence\", \"crawl-breadth\", \"session-integrity\"], action: \"allow\" },\n }),\n\n human({\n id: \"tab-restore-burst\",\n title: \"Restoring twelve pinned tabs at once after a browser restart\",\n category: \"behaviour\",\n provenance: \"A browser reopening a session issues a dozen navigations within a second\",\n notes: \"A burst that looks exactly like a flood, from one person pressing one button. It is why rate is capped at moderate and cannot deny service.\",\n requests: Array.from({ length: 12 }, (_, index) => ({\n ...browser(\"chromeWindows\", { cookie: \"sid=restore-1\" }),\n path: `/products/${index + 1}`,\n atMs: index * 40,\n })),\n expect: { certain: false, action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\", \"rate-limit\"] },\n tags: [\"known-cost\"],\n }),\n\n human({\n id: \"documentation-reader\",\n title: \"A developer clicking through a documentation sidebar\",\n category: \"behaviour\",\n provenance: \"High distinct-path count with almost no revisits — the same shape as a crawler\",\n notes: \"Genuinely indistinguishable from enumeration by path pattern alone, which is why crawl-breadth is only `weak`.\",\n requests: humanPaced(browser(\"firefoxWindows\", { kind: \"same-origin-navigate\", cookie: \"sid=docs-9\", referer: \"https://shop.example/docs\" }), [\n \"/docs/intro\", \"/docs/install\", \"/docs/config\", \"/docs/api/client\", \"/docs/api/server\", \"/docs/api/types\",\n \"/docs/guides/auth\", \"/docs/guides/deploy\", \"/docs/faq\", \"/docs/changelog\", \"/docs/api/errors\", \"/docs/api/events\",\n ]),\n expect: { certain: false, action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\", \"rate-limit\"] },\n tags: [\"known-cost\"],\n }),\n\n human({\n id: \"form-submission\",\n title: \"Submitting a login form\",\n category: \"behaviour\",\n provenance: \"POST with Origin and Referer, Sec-Fetch-Site: same-origin, Sec-Fetch-Mode: navigate\",\n requests: [\n { ...browser(\"chromeWindows\", { kind: \"same-origin-navigate\", cookie: \"sid=login-1\", referer: \"https://shop.example/login\" }), method: \"POST\", path: \"/login\" },\n ],\n expect: { verdict: \"unknown\", maxScore: 0, action: [\"allow\", \"delay\"] },\n }),\n\n human({\n id: \"conditional-revalidation\",\n title: \"A cache revalidation from a returning visitor\",\n category: \"behaviour\",\n provenance: \"Chrome adds If-None-Match and Cache-Control when revisiting a cached page\",\n requests: [withCacheValidators(browser(\"chromeWindows\", { kind: \"navigate\", cookie: \"sid=return-4\" }))],\n expect: { verdict: \"unknown\", maxScore: 0, action: \"allow\" },\n }),\n\n // ---------------------------------------------------------------------------\n // People behind infrastructure that mangles their requests. The site sees the\n // intermediary, not the person, and the person pays for it.\n // ---------------------------------------------------------------------------\n human({\n id: \"corporate-proxy-stripped\",\n title: \"A person behind a corporate proxy that strips Sec-Fetch and Client Hints\",\n category: \"mangled-by-infrastructure\",\n provenance: \"Enterprise TLS-inspecting proxies routinely rebuild requests and drop headers they do not understand\",\n notes: \"Two of the strongest impersonation signals fire on a real employee. Neither may deny service; both are capped below `certain` for this reason.\",\n requests: [\n {\n headers: [\n [\"Host\", \"shop.example\"],\n [\"Connection\", \"keep-alive\"],\n [\"User-Agent\", userAgentOf(\"chromeWindows\")],\n [\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"],\n [\"Accept-Encoding\", \"gzip, deflate\"],\n [\"Accept-Language\", \"en-US,en;q=0.9\"],\n [\"Via\", \"1.1 corporate-proxy.internal (squid/6.6)\"],\n [\"X-Forwarded-For\", \"10.14.2.88\"],\n ],\n protocol: \"https\",\n httpVersion: \"1.1\",\n },\n ],\n expect: { certain: false, action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\", \"rate-limit\"] },\n tags: [\"known-cost\"],\n }),\n\n human({\n id: \"carrier-transcoder\",\n title: \"A person on a mobile carrier that transcodes pages\",\n category: \"mangled-by-infrastructure\",\n provenance: \"Some carriers proxy and rewrite requests, adding their own headers and reordering the rest\",\n requests: [\n {\n headers: [\n [\"Host\", \"shop.example\"],\n [\"Accept-Encoding\", \"gzip\"],\n [\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"],\n [\"User-Agent\", userAgentOf(\"chromeAndroid\")],\n [\"Accept-Language\", \"en-GB,en;q=0.9\"],\n [\"Via\", \"1.1 wtp-proxy\"],\n [\"X-Forwarded-For\", \"100.64.12.9\"],\n ],\n protocol: \"https\",\n httpVersion: \"1.1\",\n },\n ],\n expect: { certain: false, action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\", \"rate-limit\"] },\n notes: \"Accept-Encoding arrives before Accept — the same ordering python-requests produces. A person, via a carrier.\",\n tags: [\"known-cost\"],\n }),\n\n human({\n id: \"http2-normalised\",\n title: \"A browser over HTTP/2, where header order carries no meaning\",\n category: \"mangled-by-infrastructure\",\n provenance: \"HTTP/2 uses HPACK and does not preserve a meaningful header order\",\n notes:\n \"The header-order detector must stand down entirely rather than read the normalised order as a fingerprint. Note also what is *absent*: HTTP/2 forbids connection-specific headers, so a genuine h2 request has no Connection. Leaving one in while claiming h2 — which an adapter does if it reads the version from a forwarded header rather than from its own socket — manufactures a proven protocol violation for a real browser.\",\n requests: [\n {\n ...browser(\"chromeWindows\"),\n httpVersion: \"2.0\",\n headers: browser(\"chromeWindows\").headers.filter(([name]) => name.toLowerCase() !== \"connection\"),\n },\n ],\n expect: { verdict: \"unknown\", maxScore: 0, notDetectors: [\"header-order\"], action: \"allow\" },\n }),\n\n human({\n id: \"cgnat-shared-address\",\n title: \"Many people behind one carrier-grade NAT address\",\n category: \"mangled-by-infrastructure\",\n provenance: \"CGNAT presents thousands of subscribers as a single address; RFC 6598 reserves 100.64.0.0/10 for it\",\n notes: \"Different devices, different browsers, one address. Under an IP-based actor key this is one very busy 'actor' — which is why identity-rotation ships disabled by default.\",\n requests: [\n { ...browser(\"safariIos\"), ip: \"100.64.3.17\", atMs: 0 },\n { ...browser(\"chromeAndroid\"), ip: \"100.64.3.17\", atMs: 220 },\n { ...browser(\"samsungInternet\"), ip: \"100.64.3.17\", atMs: 480 },\n { ...browser(\"safariIos\"), ip: \"100.64.3.17\", atMs: 700 },\n { ...browser(\"chromeAndroid\"), ip: \"100.64.3.17\", atMs: 910 },\n { ...browser(\"chromeWindows\"), ip: \"100.64.3.17\", atMs: 1_150 },\n ],\n expect: { certain: false, action: [\"allow\", \"tag\", \"log\", \"delay\", \"challenge\", \"rate-limit\"] },\n tags: [\"known-cost\"],\n }),\n\n // ---------------------------------------------------------------------------\n // People doing the things a probe detector was built to notice.\n //\n // Every path-based signal has a population of real people who ask for the same\n // thing for an ordinary reason, and these are them. Both cases are tagged\n // `known-cost` because both do accumulate some suspicion — the point is the size of\n // it: enough to appear in a dashboard, nowhere near enough to interrupt anybody.\n // ---------------------------------------------------------------------------\n human({\n id: \"wordpress-author-signing-in\",\n title: \"An author signing in to their own WordPress site\",\n category: \"platform-front-door\",\n provenance: \"/wp-login.php is a probe on the sites that do not run WordPress and the front door on the roughly forty per cent that do\",\n notes:\n \"`probe-signature` reports this at `moderate` and says so in its metadata, which is the honest reading: from a single request the library cannot know whether this site runs the platform. Configure the detector's `ignore` list, or the engine's `ignorePaths`, if it does — and note what the cap buys in the meantime, which is that forgetting costs a tag rather than a locked-out author.\",\n requests: [{ ...browser(\"chromeWindows\", { kind: \"same-origin-navigate\", referer: \"https://shop.example/\" }), path: \"/wp-login.php\" }],\n expect: { verdict: \"unknown\", maxScore: 40, action: [\"allow\", \"tag\", \"log\"] },\n tags: [\"known-cost\"],\n }),\n human({\n id: \"developer-searching-for-sql-syntax\",\n title: \"Someone searching a documentation site for SQL syntax\",\n category: \"platform-front-door\",\n provenance: \"A search box on a site whose subject is databases, with the phrase a payload detector looks for typed into it\",\n notes:\n \"The reason the payload tiers are split by punctuation. `union select` typed into a search box is a person reading about SQL; `' union select` with the quote that makes it execute is not. Without that split, the population penalised most is the one reading documentation about the attack.\",\n requests: [{ ...browser(\"chromeWindows\", { kind: \"same-origin-navigate\", referer: \"https://shop.example/docs\" }), path: \"/search?q=union+select+examples\" }],\n // `rate-limit` is in the list because `protect-data` rate-limits search endpoints\n // for everybody, which is a policy decision about the path rather than a judgement\n // about this client. What the case asserts is the score.\n expect: { verdict: \"unknown\", maxScore: 40, action: [\"allow\", \"tag\", \"log\", \"rate-limit\"] },\n tags: [\"known-cost\"],\n }),\n human({\n id: \"returning-reader-revalidating\",\n title: \"A returning reader whose browser still holds the page\",\n category: \"ordinary-browsing\",\n provenance: \"A revisit to a page the browser cached, sending the validators it was given\",\n notes:\n \"The evidence here points the other way. A client that revalidates a cached copy has been here before and kept what it was served, which is a property of a browsing session rather than of a fetch loop — `browsing-coherence` reports it as human-pointing, and the engine discounts any suspicion the behavioural detectors raise.\",\n requests: [\n { ...browser(\"chromeWindows\", { kind: \"navigate\", cookie: \"session=8f2c1b; consent=1\" }), path: \"/guides/shipping\", atMs: 0 },\n { ...browser(\"chromeWindows\", { kind: \"same-origin-navigate\", cookie: \"session=8f2c1b; consent=1\", referer: \"https://shop.example/guides/shipping\", revalidate: { etag: 'W/\"41d-19a0b2f3c11\"' } }), path: \"/guides/returns\", atMs: 9_400 },\n ],\n expect: { verdict: [\"unknown\", \"human\"], maxScore: 20, detectors: [\"browsing-coherence\"], action: [\"allow\", \"tag\", \"log\"] },\n }),\n];\n","import { browser, plain } from \"./headers.js\";\nimport { bot, human } from \"./schema.js\";\nimport type { TrafficCase } from \"./schema.js\";\n\n/**\n * Traffic from the machinery around your application.\n *\n * Load balancer probes, container orchestration health checks, your own mobile app,\n * your own server-side renderer, webhooks arriving from a payment processor, a\n * browser prefetching a page a person has not asked for yet. None of it is a person,\n * almost none of it is unwelcome, and most of it will be classified as automation\n * because that is exactly what it is.\n *\n * The lesson this section is here to teach is that **the answer is usually the\n * allowlist or `ignorePaths`, not a detector**. A health check from your own load\n * balancer should never reach detection at all: it costs work, it inflates every\n * per-actor counter with perfectly regular traffic, and the one thing you must never\n * do is let a bot policy decide whether your orchestrator thinks you are alive.\n */\n\nexport const INFRASTRUCTURE_CASES: TrafficCase[] = [\n // ---------------------------------------------------------------------------\n // Probes. These belong in ignorePaths.\n // ---------------------------------------------------------------------------\n bot({\n id: \"kubernetes-probe\",\n title: \"A Kubernetes liveness probe\",\n audience: \"infrastructure\",\n category: \"health-probe\",\n provenance: \"kubelet sends kube-probe/<version> with no Accept-Language and no cookies, on a perfect interval\",\n notes:\n \"Machine-regular by design, from a fixed address, forever. Put the path in `ignorePaths`: letting a bot policy decide whether your orchestrator believes the pod is healthy is a way to turn a false positive into a restart loop.\",\n requests: Array.from({ length: 10 }, (_, index) => ({ ...plain(\"kube-probe/1.31\"), path: \"/healthz\", atMs: index * 10_000, ip: \"10.42.0.1\" })),\n expect: { neverAction: [\"block\", \"drop\", \"redirect\"] },\n tags: [\"allowlist-candidate\"],\n }),\n bot({\n id: \"elb-health-checker\",\n title: \"An AWS load balancer health check\",\n audience: \"infrastructure\",\n category: \"health-probe\",\n provenance: \"ELB-HealthChecker/2.0\",\n requests: Array.from({ length: 8 }, (_, index) => ({ ...plain(\"ELB-HealthChecker/2.0\"), path: \"/healthz\", atMs: index * 15_000, ip: \"10.0.3.44\" })),\n expect: { neverAction: [\"block\", \"drop\", \"redirect\"] },\n tags: [\"allowlist-candidate\"],\n }),\n bot({\n id: \"prometheus-blackbox\",\n title: \"A Prometheus blackbox exporter probe\",\n audience: \"infrastructure\",\n category: \"health-probe\",\n provenance: \"Blackbox exporter identifies itself and runs on a scrape interval\",\n requests: Array.from({ length: 6 }, (_, index) => ({ ...plain(\"Prometheus/2.54.1 blackbox_exporter/0.25.0\"), path: \"/metrics\", atMs: index * 30_000, ip: \"10.0.9.12\" })),\n expect: { neverAction: [\"block\", \"drop\", \"redirect\"] },\n tags: [\"allowlist-candidate\"],\n }),\n\n // ---------------------------------------------------------------------------\n // Your own software.\n // ---------------------------------------------------------------------------\n bot({\n id: \"first-party-mobile-app\",\n title: \"Your own iOS app calling your own API\",\n audience: \"infrastructure\",\n category: \"first-party-client\",\n provenance: \"A native app using URLSession identifies itself as the app, not as a browser\",\n notes:\n \"Automation by every measure the library has, and a paying customer holding a phone. Nothing in a request distinguishes the two — which is what `isHuman` is for: your application knows this session is authenticated, and the library does not.\",\n requests: [\n {\n headers: [\n [\"Host\", \"api.shop.example\"],\n [\"Accept\", \"application/json\"],\n [\"Authorization\", \"Bearer redacted\"],\n [\"User-Agent\", \"ShopApp/4.12.0 (com.example.shop; build:4120; iOS 18.5.0) Alamofire/5.9.1\"],\n [\"Accept-Language\", \"en-GB;q=1.0\"],\n [\"Accept-Encoding\", \"br;q=1.0, gzip;q=0.9, deflate;q=0.8\"],\n ],\n path: \"/v1/basket\",\n protocol: \"https\",\n httpVersion: \"1.1\",\n },\n ],\n expect: { neverAction: [\"block\", \"drop\", \"redirect\"] },\n tags: [\"allowlist-candidate\"],\n }),\n bot({\n id: \"server-side-render\",\n title: \"Your own renderer fetching your own API\",\n audience: \"infrastructure\",\n category: \"first-party-client\",\n provenance: \"A Next.js server component calling an internal endpoint with undici\",\n requests: [\n {\n headers: [[\"Host\", \"api.shop.example\"], [\"Accept\", \"application/json\"], [\"User-Agent\", \"undici\"], [\"Accept-Encoding\", \"gzip, deflate\"]],\n path: \"/v1/products\",\n protocol: \"https\",\n httpVersion: \"1.1\",\n ip: \"10.0.5.20\",\n },\n ],\n expect: { neverAction: [\"block\", \"drop\", \"redirect\"] },\n tags: [\"allowlist-candidate\"],\n }),\n bot({\n id: \"stripe-webhook\",\n title: \"A payment webhook arriving from Stripe\",\n audience: \"infrastructure\",\n category: \"webhook\",\n provenance: \"Stripe posts events with its own User-Agent and a signature header\",\n notes:\n \"Blocking this loses orders silently, and the retry backoff means you find out hours later. Webhook endpoints belong in `ignorePaths` — they authenticate themselves cryptographically and have no use for bot detection.\",\n requests: [\n {\n headers: [\n [\"Host\", \"shop.example\"],\n [\"Accept\", \"*/*; q=0.5, application/xml\"],\n [\"Content-Type\", \"application/json; charset=utf-8\"],\n [\"Stripe-Signature\", \"t=1756544400,v1=redacted\"],\n [\"User-Agent\", \"Stripe/1.0 (+https://stripe.com/docs/webhooks)\"],\n ],\n method: \"POST\",\n path: \"/webhooks/stripe\",\n protocol: \"https\",\n httpVersion: \"1.1\",\n },\n ],\n expect: { neverAction: [\"block\", \"drop\", \"redirect\"] },\n tags: [\"allowlist-candidate\"],\n }),\n bot({\n id: \"github-webhook\",\n title: \"A GitHub webhook\",\n audience: \"infrastructure\",\n category: \"webhook\",\n provenance: \"GitHub-Hookshot/<sha>\",\n requests: [\n {\n headers: [[\"Host\", \"shop.example\"], [\"User-Agent\", \"GitHub-Hookshot/f1a2b3c\"], [\"Content-Type\", \"application/json\"], [\"X-GitHub-Event\", \"push\"], [\"Accept\", \"*/*\"]],\n method: \"POST\",\n path: \"/webhooks/github\",\n protocol: \"https\",\n httpVersion: \"1.1\",\n },\n ],\n expect: { neverAction: [\"block\", \"drop\", \"redirect\"] },\n tags: [\"allowlist-candidate\"],\n }),\n\n // ---------------------------------------------------------------------------\n // Browser-initiated requests that no person asked for.\n // ---------------------------------------------------------------------------\n human({\n id: \"speculation-rules-prefetch\",\n title: \"A browser prefetching a page the person has not clicked yet\",\n category: \"browser-initiated\",\n provenance: \"Speculation Rules mark prefetches with Sec-Purpose: prefetch\",\n notes:\n \"A real browser, a real person, and a request they never made — issued speculatively while they hover a link. It arrives with no Sec-Fetch-User, because no user gesture triggered it. Reading that absence as evidence would penalise the person for their browser being fast.\",\n requests: [\n {\n ...browser(\"chromeWindows\", { kind: \"same-origin-navigate\", referer: \"https://shop.example/products\" }),\n headers: [\n ...browser(\"chromeWindows\", { kind: \"same-origin-navigate\", referer: \"https://shop.example/products\" }).headers.filter(([name]) => name !== \"Sec-Fetch-User\"),\n [\"Sec-Purpose\", \"prefetch\"],\n [\"Purpose\", \"prefetch\"],\n ],\n path: \"/products/88\",\n },\n ],\n expect: { certain: false, neverAction: [\"block\", \"drop\", \"redirect\"] },\n }),\n human({\n id: \"service-worker-fetch\",\n title: \"A service worker refreshing cached content in the background\",\n category: \"browser-initiated\",\n provenance: \"Sec-Fetch-Dest: empty with Sec-Fetch-Mode: cors, issued with no tab in the foreground\",\n requests: [{ ...browser(\"chromeWindows\", { kind: \"xhr\" }), path: \"/api/catalog.json\" }],\n expect: { verdict: \"unknown\", maxScore: 20, neverAction: [\"block\", \"drop\", \"redirect\"] },\n }),\n\n // ---------------------------------------------------------------------------\n // People arriving from address space that looks automated.\n // ---------------------------------------------------------------------------\n human({\n id: \"consumer-vpn-exit\",\n title: \"A person browsing through a consumer VPN\",\n category: \"datacenter-human\",\n provenance: \"VPN exit nodes live in hosting-provider address space, which is also where scrapers live\",\n notes:\n \"A perfect browser request from an address that any datacenter range list will flag. This is why `ip-intelligence` caps datacenter matches at `moderate`: the population using a VPN is overwhelmingly people, and disproportionately people with reasons.\",\n requests: [{ ...browser(\"chromeMac\"), ip: \"192.0.2.150\" }],\n expect: { certain: false, neverAction: [\"block\", \"drop\", \"redirect\"] },\n tags: [\"known-cost\"],\n }),\n human({\n id: \"icloud-private-relay\",\n title: \"A person on iCloud Private Relay\",\n category: \"datacenter-human\",\n provenance: \"Private Relay egresses from Apple's partner networks; the address never belongs to the subscriber\",\n requests: [{ ...browser(\"safariIos\"), ip: \"192.0.2.151\" }],\n expect: { certain: false, neverAction: [\"block\", \"drop\", \"redirect\"] },\n tags: [\"known-cost\"],\n }),\n human({\n id: \"corporate-egress-shared\",\n title: \"An office of two hundred people behind one address\",\n category: \"datacenter-human\",\n provenance: \"A single corporate egress address carrying an entire building's traffic\",\n notes: \"Rate counting sees one extraordinarily busy actor. It is two hundred ordinary ones.\",\n requests: Array.from({ length: 45 }, (_, index) => ({\n ...browser(index % 3 === 0 ? \"chromeWindows\" : index % 3 === 1 ? \"edgeWindows\" : \"firefoxWindows\"),\n path: `/products/${(index % 12) + 1}`,\n ip: \"192.0.2.200\",\n atMs: index * 180,\n })),\n expect: { certain: false, neverAction: [\"block\", \"drop\", \"redirect\"] },\n tags: [\"known-cost\"],\n }),\n\n // ---------------------------------------------------------------------------\n // Forwarding headers. The highest-consequence configuration in the library.\n // ---------------------------------------------------------------------------\n bot({\n id: \"xff-spoof-attempt\",\n title: \"A client prepending a fake hop to X-Forwarded-For\",\n audience: \"hostile\",\n category: \"forwarding\",\n provenance: \"X-Forwarded-For is client-supplied; anyone can prepend an address and choose the identity they are tracked under\",\n notes:\n \"The corpus cannot assert the resolved address directly, so this case asserts the consequence: the client's chosen address must not let it escape its own classification. A curl request stays a curl request whatever it writes in the header.\",\n requests: [\n {\n headers: [[\"Host\", \"shop.example\"], [\"User-Agent\", \"curl/8.11.1\"], [\"Accept\", \"*/*\"], [\"X-Forwarded-For\", \"66.249.66.1, 192.0.2.90\"]],\n ip: \"192.0.2.90\",\n protocol: \"https\",\n httpVersion: \"1.1\",\n },\n ],\n expect: { verdict: \"confirmed-bot\", botClass: \"http-client\", certain: true },\n tags: [\"security\"],\n }),\n];\n","import { browser, plain } from \"./headers.js\";\nimport { bot, human } from \"./schema.js\";\nimport type { TrafficCase } from \"./schema.js\";\n\n/**\n * Address reputation and prior clearance.\n *\n * Two mechanisms that only work when you have configured them, and that behave very\n * differently from everything else in the corpus:\n *\n * **Address ranges** are operator data. The library ships none, because an\n * address-to-operator mapping goes stale within weeks and a stale mapping is a false\n * positive with a long half-life. A denylist entry is `certain` — not because we\n * deduced anything, but because you decided it — while a datacenter match is capped\n * at `moderate` forever, since consumer VPNs, corporate gateways, Tor exits and\n * privacy relays all live in the same address space as the scrapers.\n *\n * **Clearance** is the only source of human-pointing evidence the library has, and\n * the levels are not interchangeable. A solved proof of work shows that a JavaScript\n * engine ran and CPU was spent — a headless Chrome does both. Only `operator`\n * clearance, which your own application issues on evidence the request does not\n * carry, is treated as conclusive.\n */\n\nexport const REPUTATION_CASES: TrafficCase[] = [\n bot({\n id: \"denylist-hit\",\n title: \"An address the operator has denied\",\n audience: \"hostile\",\n category: \"address-reputation\",\n provenance: \"An explicit local decision, not an inference\",\n notes:\n \"`certain`, and the justification is about responsibility rather than technology: nothing was deduced, an instruction was carried out. It also means a bad denylist entry blocks real people with no probabilistic guard to catch it — which is the argument for reviewing the list rather than growing it.\",\n requires: [\"denylist\"],\n requests: [{ ...browser(\"chromeWindows\"), ip: \"203.0.113.244\" }],\n expect: { verdict: \"confirmed-bot\", certain: true, detectors: [\"ip-intelligence\"] },\n }),\n bot({\n id: \"datacenter-scraper\",\n title: \"A scraper from hosting-provider address space\",\n audience: \"unwanted-bot\",\n category: \"address-reputation\",\n provenance: \"Where bulk extraction is actually run from\",\n notes: \"The corroborating case: a datacenter address alongside a bare library client is a different proposition from either alone.\",\n requires: [\"datacenter-ranges\"],\n requests: [{ ...plain(\"python-requests/2.32.3\"), ip: \"192.0.2.180\" }],\n expect: { verdict: \"confirmed-bot\", botClass: \"http-client\", certain: true, detectors: [\"ip-intelligence\", \"self-identified\"] },\n }),\n human({\n id: \"datacenter-human-with-ranges\",\n title: \"A person on a VPN, with datacenter ranges configured\",\n category: \"address-reputation\",\n provenance: \"The same address space, a completely different client\",\n notes:\n \"The whole reason the datacenter signal is capped at `moderate`. With ranges loaded this person now carries a bot-pointing observation on every request, and it must never be enough to deny them.\",\n requires: [\"datacenter-ranges\"],\n requests: [{ ...browser(\"firefoxWindows\"), ip: \"192.0.2.190\" }],\n expect: { certain: false, detectors: [\"ip-intelligence\"] },\n tags: [\"known-cost\"],\n }),\n\n human({\n id: \"cleared-by-proof-of-work\",\n title: \"A person who already solved the challenge\",\n category: \"clearance\",\n provenance: \"A signed clearance cookie from a completed proof of work\",\n notes:\n \"Only `moderate` human evidence. Proof of work demonstrates a JavaScript engine and spent CPU; it does not demonstrate a person, and treating it as though it did would be the mirror image of the mistake this library exists to avoid.\",\n clearance: \"pow\",\n requests: [browser(\"chromeWindows\")],\n expect: { detectors: [\"clearance\"], neverAction: [\"block\", \"drop\", \"redirect\"] },\n }),\n human({\n id: \"cleared-by-operator\",\n title: \"A signed-in customer your application vouches for\",\n category: \"clearance\",\n provenance: \"`grantClearance(facts, 'operator')` after a successful login\",\n notes:\n \"The only conclusive human signal in the library, and it does not come from the request. Your application knows this session is authenticated; the library cannot see that and does not try to guess it.\",\n clearance: \"operator\",\n requests: [browser(\"chromeWindows\")],\n expect: { verdict: \"human\", certain: true, detectors: [\"clearance\"], action: [\"allow\", \"log\", \"tag\"] },\n }),\n bot({\n id: \"cleared-but-proven-bot\",\n title: \"A proven bot presenting a valid clearance token\",\n audience: \"unwanted-bot\",\n category: \"clearance\",\n provenance: \"A headless browser solves a proof of work as readily as a laptop does\",\n notes:\n \"Proven bot evidence outranks clearance, deliberately. This is also the shape that used to livelock: passing a challenge cannot undo a `certain` verdict, so a client that could run JavaScript was challenged, solved, reloaded and was challenged again forever. The action layer now refuses to re-issue a challenge to an actor that already holds one.\",\n clearance: \"pow\",\n requests: [plain(\"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/152.0.0.0 Safari/537.36\")],\n // Asserted on the *outcome*, not the decision: a policy is free to decide\n // \"challenge\" here, and the action layer is required to refuse to issue it.\n expect: { verdict: \"confirmed-bot\", botClass: \"automation\", certain: true, outcome: \"continue\" },\n tags: [\"regression\"],\n }),\n];\n","import { bot } from \"./schema.js\";\nimport type { Header } from \"./headers.js\";\nimport type { CaseRequest, TrafficCase } from \"./schema.js\";\n\n/**\n * HTTP clients across every ecosystem that shows up in a server log.\n *\n * Each entry reproduces the client's **default header set in its default order**,\n * because that order is a fingerprint in its own right and the differences between\n * these libraries are the whole point: `requests` sends `Accept-Encoding` before\n * `Accept`; Go sends no `Accept` at all; `urllib` puts `Host` second; OkHttp sends\n * `Connection` before `Accept-Encoding`. No browser does any of those things.\n *\n * None of this is evidence of hostility. It is evidence that nobody is looking at the\n * response — a narrower and much safer claim, and one that is usually about somebody's\n * own integration. The right treatment is an allowlist entry or a challenge, not a\n * block.\n */\n\nfunction client(\n id: string,\n title: string,\n headers: readonly Header[],\n provenance: string,\n options: { identity?: string; certain?: boolean; automation?: boolean; suspectedOnly?: boolean; botClass?: \"http-client\" | \"declared-bot\"; notes?: string; path?: string; method?: string; audience?: TrafficCase[\"audience\"] } = {},\n): TrafficCase {\n const request: CaseRequest = {\n headers: [[\"Host\", \"shop.example\"], ...headers],\n protocol: \"https\",\n httpVersion: \"1.1\",\n ...(options.path !== undefined ? { path: options.path } : {}),\n ...(options.method !== undefined ? { method: options.method } : {}),\n };\n return bot({\n id,\n title,\n audience: options.audience ?? \"unwanted-bot\",\n category: \"http-library\",\n provenance,\n ...(options.notes !== undefined ? { notes: options.notes } : {}),\n requests: [request],\n expect: options.automation === true\n ? { verdict: \"confirmed-bot\", botClass: \"automation\", certain: true }\n : options.suspectedOnly === true\n ? { verdict: \"suspected-bot\", botClass: \"http-client\", certain: false, detectors: [\"self-identified\"] }\n : options.certain === false\n ? { certain: false, botClass: [\"http-client\", \"unknown\"] }\n : {\n verdict: \"confirmed-bot\",\n botClass: options.botClass ?? \"http-client\",\n certain: true,\n ...(options.identity !== undefined ? { identity: options.identity } : {}),\n detectors: [\"self-identified\"],\n },\n });\n}\n\nexport const EXTENDED_LIBRARY_CASES: TrafficCase[] = [\n // ---- Python ----\n client(\"lib-requests-session\", \"requests with a Session and keep-alive\", [[\"User-Agent\", \"python-requests/2.32.4\"], [\"Accept-Encoding\", \"gzip, deflate, zstd\"], [\"Accept\", \"*/*\"], [\"Connection\", \"keep-alive\"]], \"requests 2.32 added zstd to its default Accept-Encoding\", { identity: \"python\" }),\n client(\"lib-requests-json-post\", \"requests posting JSON\", [[\"User-Agent\", \"python-requests/2.32.4\"], [\"Accept-Encoding\", \"gzip, deflate\"], [\"Accept\", \"*/*\"], [\"Connection\", \"keep-alive\"], [\"Content-Type\", \"application/json\"], [\"Content-Length\", \"142\"]], \"The shape of a scripted API call\", { identity: \"python\", method: \"POST\", path: \"/api/v1/orders\" }),\n client(\"lib-httpx-async\", \"httpx in async mode\", [[\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"gzip, deflate, br, zstd\"], [\"Connection\", \"keep-alive\"], [\"User-Agent\", \"python-httpx/0.28.1\"]], \"httpx sorts its defaults differently from requests and sends User-Agent last\", { identity: \"python\" }),\n client(\"lib-aiohttp-client\", \"aiohttp\", [[\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"gzip, deflate\"], [\"User-Agent\", \"Python/3.13 aiohttp/3.11.11\"]], \"aiohttp names the interpreter version ahead of its own\", { identity: \"python\" }),\n client(\"lib-urllib3-direct\", \"urllib3 used directly\", [[\"User-Agent\", \"python-urllib3/2.3.0\"], [\"Accept-Encoding\", \"identity\"]], \"urllib3 below requests sends a bare identity encoding\", { identity: \"python\" }),\n client(\"lib-python-urllib\", \"the standard library's urllib\", [[\"Accept-Encoding\", \"identity\"], [\"User-Agent\", \"Python-urllib/3.13\"], [\"Connection\", \"close\"]], \"urllib emits Accept-Encoding before Host and sends no Accept at all\", { identity: \"python\" }),\n client(\"lib-scrapy-crawler\", \"Scrapy\", [[\"User-Agent\", \"Scrapy/2.12.0 (+https://scrapy.org)\"], [\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"], [\"Accept-Language\", \"en\"], [\"Accept-Encoding\", \"gzip, deflate\"]], \"Scrapy's default crawler headers; it names a contact URL like a polite crawler\", { identity: \"python\" }),\n client(\"lib-selenium-wire\", \"selenium-wire's underlying client\", [[\"User-Agent\", \"python-requests/2.32.4\"], [\"Accept-Encoding\", \"gzip, deflate\"], [\"Accept\", \"*/*\"], [\"X-Selenium-Wire\", \"1\"]], \"Instrumented Selenium proxies leak their own headers\", { identity: \"python\" }),\n client(\"lib-mechanicalsoup\", \"MechanicalSoup\", [[\"User-Agent\", \"python-requests/2.32.4\"], [\"Accept-Encoding\", \"gzip, deflate\"], [\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"], [\"Connection\", \"keep-alive\"]], \"A form-filling wrapper over requests, used for scripted logins\", { identity: \"python\" }),\n\n // ---- JavaScript and TypeScript ----\n client(\"lib-undici-fetch\", \"Node's built-in fetch\", [[\"Accept\", \"*/*\"], [\"Accept-Language\", \"*\"], [\"Sec-Fetch-Mode\", \"cors\"], [\"User-Agent\", \"node\"], [\"Accept-Encoding\", \"gzip, deflate\"]], \"Node's global fetch is undici; it sends a bare 'node' User-Agent and an Accept-Language of *\", { certain: false, notes: \"Two characters of User-Agent. Undici also sends Sec-Fetch-Mode, which is unusual for a non-browser and shows that Fetch Metadata alone does not imply a browser.\" }),\n client(\"lib-node-fetch-v3\", \"node-fetch v3\", [[\"Accept\", \"*/*\"], [\"User-Agent\", \"node-fetch\"], [\"Accept-Encoding\", \"gzip,deflate,br\"], [\"Connection\", \"close\"]], \"node-fetch v3 dropped the URL from its default User-Agent\", { identity: \"node\" }),\n client(\"lib-axios-node\", \"axios in Node\", [[\"Accept\", \"application/json, text/plain, */*\"], [\"User-Agent\", \"axios/1.8.4\"], [\"Accept-Encoding\", \"gzip, compress, deflate, br\"]], \"axios in Node; in a browser it cannot set User-Agent at all\", { identity: \"node\" }),\n client(\"lib-got\", \"got\", [[\"User-Agent\", \"got (https://github.com/sindresorhus/got)\"], [\"Accept-Encoding\", \"gzip, deflate, br\"]], \"got names its repository in the User-Agent\", { identity: \"node\" }),\n client(\"lib-superagent\", \"superagent\", [[\"User-Agent\", \"superagent/9.0.2\"], [\"Accept-Encoding\", \"gzip, deflate\"]], \"A long-lived Node client still common in older services\", { identity: \"node\" }),\n client(\"lib-puppeteer-fetch\", \"Puppeteer's page.goto with an overridden UA\", [[\"User-Agent\", \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/152.0.0.0 Safari/537.36\"], [\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\"], [\"Accept-Encoding\", \"gzip, deflate, br\"], [\"Accept-Language\", \"en-US\"]], \"Puppeteer leaves HeadlessChrome in the UA unless told otherwise\", { audience: \"unwanted-bot\", automation: true, identity: \"headless-chrome\" }),\n client(\"lib-deno-fetch\", \"Deno's fetch\", [[\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"gzip, br\"], [\"User-Agent\", \"Deno/2.1.9\"], [\"Accept-Language\", \"*\"]], \"Deno identifies its runtime and version\", { identity: \"node\" }),\n client(\"lib-bun-fetch\", \"Bun's fetch\", [[\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"gzip, deflate, br\"], [\"User-Agent\", \"Bun/1.2.4\"], [\"Connection\", \"keep-alive\"]], \"Bun identifies its runtime and version\", { identity: \"node\" }),\n\n // ---- JVM ----\n client(\"lib-okhttp-android\", \"OkHttp from an Android app\", [[\"User-Agent\", \"okhttp/4.12.0\"], [\"Connection\", \"Keep-Alive\"], [\"Accept-Encoding\", \"gzip\"]], \"The default client behind most Android apps; Connection precedes Accept-Encoding\", { identity: \"java\" }),\n client(\"lib-retrofit\", \"Retrofit over OkHttp\", [[\"Accept\", \"application/json\"], [\"User-Agent\", \"okhttp/4.12.0\"], [\"Connection\", \"Keep-Alive\"], [\"Accept-Encoding\", \"gzip\"]], \"Retrofit adds an Accept and leaves OkHttp's own headers in place\", { identity: \"java\" }),\n client(\"lib-jdk-httpclient\", \"The JDK HttpClient\", [[\"User-Agent\", \"Java-http-client/21.0.6\"], [\"Connection\", \"Upgrade, HTTP2-Settings\"], [\"Upgrade\", \"h2c\"], [\"HTTP2-Settings\", \"AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA\"]], \"The JDK client attempts an h2c upgrade over cleartext, which browsers never do\", { identity: \"java\" }),\n client(\"lib-apache-httpclient5\", \"Apache HttpClient 5\", [[\"User-Agent\", \"Apache-HttpClient/5.4.1 (Java/21.0.6)\"], [\"Accept-Encoding\", \"gzip, x-gzip, deflate\"], [\"Connection\", \"keep-alive\"]], \"Ubiquitous in JVM services; note the x-gzip alias no browser sends\", { identity: \"java\" }),\n client(\"lib-spring-webclient\", \"Spring WebClient over Reactor Netty\", [[\"User-Agent\", \"ReactorNetty/1.2.2\"], [\"Accept\", \"application/json\"], [\"Accept-Encoding\", \"gzip\"]], \"The reactive stack in most modern Spring services\", { certain: false }),\n client(\"lib-ktor\", \"Ktor's client\", [[\"Accept\", \"*/*\"], [\"Accept-Charset\", \"UTF-8\"], [\"User-Agent\", \"ktor-client\"], [\"Accept-Encoding\", \"gzip,deflate,identity\"]], \"Ktor still sends Accept-Charset, which browsers dropped over a decade ago\", { identity: \"java\", notes: \"Accept-Charset is a genuine period marker: no browser has sent it since 2014.\" }),\n client(\"lib-jsoup\", \"jsoup fetching a page to parse\", [[\"User-Agent\", \"Mozilla/5.0 (jsoup)\"], [\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"], [\"Accept-Encoding\", \"gzip\"]], \"jsoup wears a Mozilla preamble it did not earn\", { identity: \"java\", notes: \"A Mozilla preamble with `(jsoup)` where the platform block belongs. The preamble buys nothing: the product token names the library, and that is what is read.\" }),\n\n // ---- Go, Rust, and systems languages ----\n client(\"lib-go-nethttp\", \"Go's net/http\", [[\"User-Agent\", \"Go-http-client/2.0\"], [\"Accept-Encoding\", \"gzip\"]], \"Go sends only a User-Agent and Accept-Encoding by default — no Accept at all\", { identity: \"go\" }),\n client(\"lib-go-resty\", \"Go with resty\", [[\"User-Agent\", \"go-resty/2.16.5 (https://github.com/go-resty/resty)\"], [\"Accept-Encoding\", \"gzip\"]], \"resty names its repository\", { identity: \"go\" }),\n client(\"lib-rust-reqwest\", \"Rust's reqwest\", [[\"Accept\", \"*/*\"], [\"User-Agent\", \"reqwest/0.12.12\"], [\"Accept-Encoding\", \"gzip, br, zstd, deflate\"]], \"reqwest advertises four encodings including zstd\", { identity: \"rust\" }),\n client(\"lib-rust-ureq\", \"Rust's ureq\", [[\"User-Agent\", \"ureq/3.0.5\"], [\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"gzip\"]], \"A blocking Rust client common in CLI tools\", { identity: \"rust\" }),\n client(\"lib-curl-cli\", \"curl from a shell\", [[\"User-Agent\", \"curl/8.12.1\"], [\"Accept\", \"*/*\"]], \"curl sends exactly three headers by default and no Accept-Encoding unless asked\", { identity: \"curl\" }),\n client(\"lib-curl-compressed\", \"curl --compressed\", [[\"User-Agent\", \"curl/8.12.1\"], [\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"deflate, gzip, br, zstd\"]], \"The --compressed flag adds every encoding libcurl was built with\", { identity: \"curl\" }),\n client(\"lib-wget\", \"Wget\", [[\"User-Agent\", \"Wget/1.25.0\"], [\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"identity\"], [\"Connection\", \"Keep-Alive\"]], \"Wget defaults to identity encoding\", { identity: \"wget\" }),\n client(\"lib-wget2\", \"Wget2\", [[\"User-Agent\", \"Wget/2.2.0\"], [\"Accept-Encoding\", \"gzip, br, zstd, lzip, deflate\"], [\"Accept\", \"*/*\"], [\"Connection\", \"keep-alive\"]], \"Wget2 advertises encodings no browser supports, including lzip\", { identity: \"wget\" }),\n client(\"lib-aria2\", \"aria2 download manager\", [[\"User-Agent\", \"aria2/1.37.0\"], [\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"deflate, gzip\"], [\"Connection\", \"close\"], [\"Range\", \"bytes=0-1048575\"]], \"aria2 segments downloads with parallel Range requests\", { identity: \"misc-cli\", path: \"/downloads/catalogue.pdf\" }),\n\n // ---- PHP, Ruby, Perl, .NET ----\n client(\"lib-guzzle\", \"Guzzle\", [[\"User-Agent\", \"GuzzleHttp/7.9.2 curl/8.12.1 PHP/8.4.3\"], [\"Accept\", \"*/*\"]], \"Guzzle names itself, libcurl and PHP in one string\", { identity: \"php\" }),\n client(\"lib-php-file-get-contents\", \"PHP's file_get_contents\", [[\"User-Agent\", \"PHP/8.4.3\"], [\"Accept\", \"*/*\"], [\"Connection\", \"close\"]], \"The default stream wrapper sends the bare interpreter version\", { identity: \"php\" }),\n client(\"lib-wordpress-pingback\", \"A WordPress pingback\", [[\"User-Agent\", \"WordPress/6.8.1; https://blog.example\"], [\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"deflate;q=1.0, compress;q=0.5, gzip;q=0.5\"], [\"Content-Type\", \"application/x-www-form-urlencoded\"]], \"WordPress names the calling site in its User-Agent, which makes it traceable\", { identity: \"php\", method: \"POST\", path: \"/xmlrpc.php\", notes: \"Honest automation from somebody else's blog. A block here breaks a pingback, not an attack.\" }),\n client(\"lib-drupal-http\", \"Drupal's HTTP client\", [[\"User-Agent\", \"Drupal/11.1 (+https://www.drupal.org/)\"], [\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"gzip\"]], \"Drupal names its project URL\", { identity: \"php\" }),\n client(\"lib-ruby-faraday\", \"Ruby with Faraday\", [[\"User-Agent\", \"Faraday v2.12.2\"], [\"Accept-Encoding\", \"gzip;q=1.0,deflate;q=0.6,identity;q=0.3\"], [\"Accept\", \"*/*\"]], \"Faraday's quality-weighted encoding list is distinctive\", { identity: \"ruby\" }),\n client(\"lib-ruby-nethttp\", \"Ruby's Net::HTTP\", [[\"Accept-Encoding\", \"gzip;q=1.0,deflate;q=0.6,identity;q=0.3\"], [\"Accept\", \"*/*\"], [\"User-Agent\", \"Ruby\"], [\"Connection\", \"close\"]], \"The standard library sends a User-Agent of exactly 'Ruby'\", { suspectedOnly: true, notes: \"Four characters. No signature matches it — a token of 'ruby' would match any string containing the word — so it is caught by shape alone, as a bare product token no browser emits. Strong, not proven, and that is the right answer.\" }),\n client(\"lib-httprb-mastodon\", \"http.rb, as used by Mastodon\", [[\"User-Agent\", \"http.rb/5.2.0 (Mastodon/4.3.4; +https://mastodon.example/)\"], [\"Accept\", \"application/activity+json, application/ld+json\"], [\"Accept-Encoding\", \"gzip\"]], \"Fediverse servers fetch on their users' behalf and name both the library and the instance\", { audience: \"benign-bot\", identity: \"mastodon\", botClass: \"declared-bot\", notes: \"One shared link can arrive from hundreds of instances at once, each a separate server acting for a real reader. Identified as Mastodon rather than as a Ruby client: the more specific claim is the more useful one for writing a rule.\" }),\n client(\"lib-dotnet-httpclient\", \".NET HttpClient\", [[\"User-Agent\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) HttpClient/9.0\"], [\"Accept-Encoding\", \"gzip, deflate, br\"], [\"Connection\", \"keep-alive\"]], \"Even library clients sometimes wear a Mozilla preamble\", { identity: \"dotnet\" }),\n client(\"lib-restsharp\", \"RestSharp\", [[\"User-Agent\", \"RestSharp/112.1.0\"], [\"Accept\", \"application/json, text/json, text/x-json, text/javascript, application/xml, text/xml\"], [\"Accept-Encoding\", \"gzip, deflate\"]], \"RestSharp's Accept list is long and unmistakably not a browser's\", { identity: \"dotnet\" }),\n client(\"lib-powershell-invoke\", \"PowerShell's Invoke-WebRequest\", [[\"User-Agent\", \"Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.26100; en-GB) PowerShell/7.5.0\"], [\"Accept-Encoding\", \"gzip, deflate, br\"]], \"PowerShell names the exact Windows build and the shell version\", { identity: \"shell\", notes: \"A Mozilla preamble again, beside a product token no browser emits. Invoke-WebRequest is a deployment script far more often than it is an attack.\" }),\n client(\"lib-libwww-perl\", \"Perl's LWP\", [[\"User-Agent\", \"libwww-perl/6.78\"], [\"TE\", \"deflate,gzip;q=0.3\"]], \"One of the oldest scripted clients still in circulation\", { identity: \"perl\" }),\n\n // ---- CLI tools people use by hand ----\n client(\"lib-httpie\", \"HTTPie\", [[\"User-Agent\", \"HTTPie/3.2.4\"], [\"Accept-Encoding\", \"gzip, deflate\"], [\"Accept\", \"*/*\"], [\"Connection\", \"keep-alive\"]], \"A command-line client aimed at people rather than at scripts\", { identity: \"misc-cli\", notes: \"Automation by shape, a developer by intent. Worth remembering when choosing an action.\" }),\n client(\"lib-postman\", \"Postman\", [[\"User-Agent\", \"PostmanRuntime/7.43.0\"], [\"Accept\", \"*/*\"], [\"Cache-Control\", \"no-cache\"], [\"Postman-Token\", \"0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9\"], [\"Accept-Encoding\", \"gzip, deflate, br\"], [\"Connection\", \"keep-alive\"]], \"Postman adds a per-request token header of its own\", { identity: \"misc-cli\" }),\n client(\"lib-insomnia\", \"Insomnia\", [[\"User-Agent\", \"insomnia/10.3.0\"], [\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"gzip, deflate, br\"]], \"An API client, a person clicking Send\", { identity: \"misc-cli\" }),\n client(\"lib-xh\", \"xh\", [[\"User-Agent\", \"xh/0.23.1\"], [\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"gzip, deflate, br, zstd\"], [\"Connection\", \"keep-alive\"]], \"A Rust reimplementation of HTTPie\", { certain: false }),\n client(\"lib-k6-load-test\", \"A k6 load test\", [[\"User-Agent\", \"k6/0.56.0 (https://k6.io/)\"], [\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"gzip\"]], \"Load generators identify themselves; usually this is your own test running against your own site\", { identity: \"k6\", audience: \"infrastructure\", notes: \"k6 is a named signature, and deliberately a `library` one rather than a `monitoring` one: monitoring is a benign category and benign categories are allowed by default, which is the wrong default for a tool whose purpose is generating load. Allowlist the load generator addresses before a test rather than discovering mid-run that the bot policy is what you measured.\" }),\n client(\"lib-vegeta\", \"A Vegeta load test\", [[\"User-Agent\", \"Go-http-client/2.0\"], [\"Accept-Encoding\", \"gzip\"], [\"X-Vegeta-Attack\", \"1\"]], \"Vegeta is built on Go's client and inherits its header set\", { identity: \"go\", audience: \"infrastructure\" }),\n client(\"lib-ab-benchmark\", \"ApacheBench\", [[\"User-Agent\", \"ApacheBench/2.3\"], [\"Accept\", \"*/*\"]], \"The oldest load tool still in daily use\", { suspectedOnly: true, audience: \"infrastructure\" }),\n];\n","import { plain } from \"./headers.js\";\nimport { bot } from \"./schema.js\";\nimport type { CaseRequest, TrafficCase } from \"./schema.js\";\n\n/**\n * Tools: HTTP libraries, automation runtimes, and security scanners.\n *\n * These are the cases the library is most confident about, and it is worth being\n * exact about *why*. A request saying `python-requests/2.32.3` is not evidence that\n * anyone is hostile — it is evidence that no human is looking at the response, which\n * is a much narrower and much safer claim. Plenty of this traffic is somebody's own\n * integration, which is why the recommended treatment is a challenge or an allowlist\n * entry rather than a block.\n *\n * Header *order* carries as much information as the User-Agent here. python-requests\n * sends `Accept-Encoding` before `Accept`; no browser does. The cases below reproduce\n * each library's real order so the ordering detector is genuinely exercised rather\n * than fed a plausible-looking guess.\n */\n\nfunction library(id: string, title: string, headers: CaseRequest[\"headers\"], provenance: string, identity?: string, notes?: string): TrafficCase {\n return bot({\n id,\n title,\n audience: \"unwanted-bot\",\n category: \"http-library\",\n provenance,\n ...(notes !== undefined ? { notes } : {}),\n requests: [{ headers, protocol: \"https\", httpVersion: \"1.1\" }],\n expect: {\n verdict: \"confirmed-bot\",\n botClass: \"http-client\",\n certain: true,\n ...(identity !== undefined ? { identity } : {}),\n detectors: [\"self-identified\"],\n },\n });\n}\n\nexport const TOOLING_CASES: TrafficCase[] = [\n // ---------------------------------------------------------------------------\n // HTTP libraries, in the header order each actually sends.\n // ---------------------------------------------------------------------------\n library(\n \"python-requests\",\n \"python-requests\",\n [[\"Host\", \"shop.example\"], [\"User-Agent\", \"python-requests/2.32.3\"], [\"Accept-Encoding\", \"gzip, deflate\"], [\"Accept\", \"*/*\"], [\"Connection\", \"keep-alive\"]],\n \"requests sends User-Agent, Accept-Encoding, Accept, Connection — Accept-Encoding ahead of Accept, which no browser does\",\n \"python\",\n \"Exercises both the signature and the header-order invariant at once.\",\n ),\n library(\n \"python-httpx\",\n \"httpx\",\n [[\"Host\", \"shop.example\"], [\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"gzip, deflate, br\"], [\"Connection\", \"keep-alive\"], [\"User-Agent\", \"python-httpx/0.27.2\"]],\n \"httpx sorts differently from requests and sends User-Agent last\",\n \"python\",\n ),\n library(\n \"python-aiohttp\",\n \"aiohttp\",\n [[\"Host\", \"shop.example\"], [\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"gzip, deflate\"], [\"User-Agent\", \"Python/3.12 aiohttp/3.10.5\"]],\n \"aiohttp's default client header set\",\n \"python\",\n ),\n library(\n \"python-urllib\",\n \"urllib\",\n [[\"Accept-Encoding\", \"identity\"], [\"Host\", \"shop.example\"], [\"User-Agent\", \"Python-urllib/3.12\"], [\"Connection\", \"close\"]],\n \"urllib sends Host *second* and Accept-Encoding: identity — a request shape no browser produces\",\n \"python\",\n ),\n library(\n \"scrapy\",\n \"Scrapy\",\n [[\"Host\", \"shop.example\"], [\"User-Agent\", \"Scrapy/2.11.2 (+https://scrapy.org)\"], [\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"], [\"Accept-Language\", \"en\"], [\"Accept-Encoding\", \"gzip, deflate\"]],\n \"Scrapy's default crawler headers; it names a contact URL like a polite crawler\",\n \"python\",\n ),\n library(\n \"curl\",\n \"curl\",\n [[\"Host\", \"shop.example\"], [\"User-Agent\", \"curl/8.11.1\"], [\"Accept\", \"*/*\"]],\n \"curl sends exactly three headers by default\",\n \"curl\",\n ),\n library(\"wget\", \"Wget\", [[\"Host\", \"shop.example\"], [\"User-Agent\", \"Wget/1.24.5\"], [\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"identity\"], [\"Connection\", \"Keep-Alive\"]], \"Wget's default header set\", \"wget\"),\n library(\n \"go-http-client\",\n \"Go's net/http\",\n [[\"Host\", \"shop.example\"], [\"User-Agent\", \"Go-http-client/2.0\"], [\"Accept-Encoding\", \"gzip\"]],\n \"Go sends only a User-Agent and Accept-Encoding by default — no Accept at all\",\n \"go\",\n ),\n library(\"node-fetch\", \"node-fetch\", [[\"Host\", \"shop.example\"], [\"Accept\", \"*/*\"], [\"User-Agent\", \"node-fetch/1.0 (+https://github.com/bitinn/node-fetch)\"], [\"Accept-Encoding\", \"gzip,deflate\"], [\"Connection\", \"close\"]], \"node-fetch default headers; it sends Accept before User-Agent, unlike any browser\", \"node\"),\n library(\"axios\", \"axios\", [[\"Host\", \"shop.example\"], [\"Accept\", \"application/json, text/plain, */*\"], [\"User-Agent\", \"axios/1.7.7\"], [\"Accept-Encoding\", \"gzip, compress, deflate, br\"]], \"axios in Node; in a browser it cannot set User-Agent at all\", \"node\"),\n library(\"okhttp\", \"OkHttp\", [[\"Host\", \"shop.example\"], [\"User-Agent\", \"okhttp/4.12.0\"], [\"Connection\", \"Keep-Alive\"], [\"Accept-Encoding\", \"gzip\"]], \"The default client for most Android apps\", \"java\"),\n library(\"java-httpclient\", \"Java HttpClient\", [[\"Host\", \"shop.example\"], [\"User-Agent\", \"Java-http-client/21.0.4\"], [\"Connection\", \"Upgrade, HTTP2-Settings\"]], \"The JDK's built-in client\", \"java\"),\n library(\"apache-httpclient\", \"Apache HttpClient\", [[\"Host\", \"shop.example\"], [\"User-Agent\", \"Apache-HttpClient/5.3.1 (Java/21.0.4)\"], [\"Accept-Encoding\", \"gzip, x-gzip, deflate\"], [\"Connection\", \"keep-alive\"]], \"Ubiquitous in JVM services\", \"java\"),\n library(\"guzzle\", \"Guzzle\", [[\"Host\", \"shop.example\"], [\"User-Agent\", \"GuzzleHttp/7.9.2 curl/8.11.1 PHP/8.3.12\"], [\"Accept\", \"*/*\"]], \"The default PHP HTTP client; note it names curl and PHP too\", \"php\"),\n library(\"wordpress\", \"WordPress\", [[\"Host\", \"shop.example\"], [\"User-Agent\", \"WordPress/6.7.1; https://blog.example\"], [\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"deflate;q=1.0, compress;q=0.5, gzip;q=0.5\"]], \"WordPress pingbacks and feed fetches identify the calling site\", \"php\", \"Honest automation from somebody else's blog. A block here breaks a pingback, not an attack.\"),\n library(\"ruby-faraday\", \"Ruby Faraday\", [[\"Host\", \"shop.example\"], [\"User-Agent\", \"Faraday v2.12.0\"], [\"Accept-Encoding\", \"gzip;q=1.0,deflate;q=0.6,identity;q=0.3\"], [\"Accept\", \"*/*\"]], \"A common Ruby client stack\", \"ruby\"),\n library(\"dotnet-httpclient\", \".NET HttpClient\", [[\"Host\", \"shop.example\"], [\"User-Agent\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) HttpClient/8.0\"], [\"Accept-Encoding\", \"gzip, deflate, br\"]], \"Note the Mozilla prefix: even library clients sometimes wear a browser preamble\", \"dotnet\"),\n library(\"postman\", \"Postman\", [[\"Host\", \"shop.example\"], [\"User-Agent\", \"PostmanRuntime/7.42.0\"], [\"Accept\", \"*/*\"], [\"Accept-Encoding\", \"gzip, deflate, br\"], [\"Connection\", \"keep-alive\"]], \"A person clicking Send in Postman — automation by shape, a human by intent\", \"misc-cli\", \"Worth remembering when choosing an action: there is a developer on the other end of this one.\"),\n library(\"httpie\", \"HTTPie\", [[\"Host\", \"shop.example\"], [\"User-Agent\", \"HTTPie/3.2.4\"], [\"Accept-Encoding\", \"gzip, deflate\"], [\"Accept\", \"*/*\"], [\"Connection\", \"keep-alive\"]], \"A command-line client aimed at people\", \"misc-cli\"),\n library(\"libwww-perl\", \"LWP\", [[\"Host\", \"shop.example\"], [\"User-Agent\", \"libwww-perl/6.77\"], [\"TE\", \"deflate,gzip;q=0.3\"]], \"One of the oldest scripted clients still in circulation\", \"perl\"),\n\n bot({\n id: \"unknown-bare-token\",\n title: \"An unrecognised internal service client\",\n audience: \"unwanted-bot\",\n category: \"http-library\",\n provenance: \"A hand-rolled User-Agent from somebody's internal integration\",\n notes:\n \"Not in any signature database and never will be. The shape alone — a bare product token with no browser preamble — is enough for `strong`, and no more. This is the case the allowlist exists for.\",\n requests: [plain(\"AcmeInventorySync/2.3\")],\n expect: { verdict: \"suspected-bot\", botClass: \"http-client\", certain: false, minScore: 55 },\n }),\n\n bot({\n id: \"no-user-agent-at-all\",\n title: \"A request with no User-Agent\",\n audience: \"unwanted-bot\",\n category: \"http-library\",\n provenance: \"Common in scripts, and also produced by some stripped-down proxies\",\n notes: \"Deliberately only `moderate`. Header-stripping intermediaries do this to real people.\",\n requests: [{ headers: [[\"Host\", \"shop.example\"], [\"Accept\", \"*/*\"]], protocol: \"https\", httpVersion: \"1.1\" }],\n expect: { certain: false, maxScore: 60 },\n }),\n\n // ---------------------------------------------------------------------------\n // Automation runtimes that are not hiding.\n // ---------------------------------------------------------------------------\n bot({\n id: \"headless-chrome-default\",\n title: \"Headless Chrome with its default User-Agent\",\n audience: \"unwanted-bot\",\n category: \"automation\",\n provenance: \"Puppeteer and Playwright leave HeadlessChrome in the UA unless told otherwise\",\n requests: [plain(\"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/152.0.0.0 Safari/537.36\")],\n expect: { verdict: \"confirmed-bot\", botClass: \"automation\", certain: true },\n }),\n bot({\n id: \"headless-chrome-client-hints\",\n title: \"Headless Chrome that hid the UA but not the Client Hints\",\n audience: \"unwanted-bot\",\n category: \"automation\",\n provenance: \"Overriding navigator.userAgent does not rewrite Sec-CH-UA, so the brand list still says HeadlessChrome\",\n notes: \"A self-declaration in a place the operator forgot to edit — which makes it a statement rather than an inference, and therefore `certain`.\",\n requests: [\n {\n headers: [\n [\"Host\", \"shop.example\"],\n [\"Connection\", \"keep-alive\"],\n [\"sec-ch-ua\", '\"HeadlessChrome\";v=\"152\", \"Chromium\";v=\"152\", \"Not(A:Brand\";v=\"24\"'],\n [\"sec-ch-ua-mobile\", \"?0\"],\n [\"sec-ch-ua-platform\", '\"Linux\"'],\n [\"User-Agent\", \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36\"],\n [\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"],\n [\"Accept-Encoding\", \"gzip, deflate, br\"],\n [\"Accept-Language\", \"en-US\"],\n ],\n protocol: \"https\",\n httpVersion: \"1.1\",\n },\n ],\n expect: { verdict: \"confirmed-bot\", botClass: \"automation\", certain: true, detectors: [\"client-hints\"] },\n }),\n bot({\n id: \"selenium-webdriver\",\n title: \"Selenium WebDriver\",\n audience: \"unwanted-bot\",\n category: \"automation\",\n provenance: \"Some grid configurations leave a webdriver token in the UA\",\n requests: [plain(\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36 selenium/4.25.0\")],\n expect: { verdict: \"confirmed-bot\", botClass: \"automation\", certain: true },\n }),\n bot({\n id: \"phantomjs\",\n title: \"PhantomJS\",\n audience: \"unwanted-bot\",\n category: \"automation\",\n provenance: \"Long unmaintained, still seen in old scraping stacks\",\n requests: [plain(\"Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1\")],\n expect: { verdict: \"confirmed-bot\", botClass: \"automation\", certain: true },\n }),\n bot({\n id: \"splash-renderer\",\n title: \"Splash, a scriptable rendering service\",\n audience: \"unwanted-bot\",\n category: \"automation\",\n provenance: \"Scrapy's JavaScript rendering companion\",\n requests: [plain(\"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/602.1 (KHTML, like Gecko) splash/3.5 Safari/602.1\")],\n expect: { verdict: \"confirmed-bot\", botClass: \"automation\", certain: true },\n }),\n\n // ---------------------------------------------------------------------------\n // Security tooling. A hit proves automation, not malice — you may be scanning\n // yourself, which is what the allowlist is for.\n // ---------------------------------------------------------------------------\n bot({\n id: \"sqlmap\",\n title: \"sqlmap\",\n audience: \"hostile\",\n category: \"scanner\",\n provenance: \"The standard SQL injection tool; announces itself by default\",\n requests: [{ ...plain(\"sqlmap/1.8.11#stable (https://sqlmap.org)\"), path: \"/products?id=1%27%20AND%20SLEEP(5)--\" }],\n expect: { verdict: \"confirmed-bot\", botClass: \"scanner\", certain: true, identity: \"sqlmap\" },\n }),\n bot({\n id: \"nikto\",\n title: \"Nikto\",\n audience: \"hostile\",\n category: \"scanner\",\n provenance: \"Web server scanner; the UA embeds a Mozilla/5.00 typo that has never been fixed\",\n requests: [{ ...plain(\"Mozilla/5.00 (Nikto/2.5.0) (Evasions:None) (Test:Port Check)\"), path: \"/admin.php\" }],\n expect: { verdict: \"confirmed-bot\", botClass: \"scanner\", certain: true, identity: \"nikto\" },\n }),\n bot({\n id: \"nuclei\",\n title: \"Nuclei\",\n audience: \"hostile\",\n category: \"scanner\",\n provenance: \"Template-driven vulnerability scanner from ProjectDiscovery\",\n requests: [{ ...plain(\"Nuclei - Open-source project (github.com/projectdiscovery/nuclei)\"), path: \"/.git/config\" }],\n expect: { verdict: \"confirmed-bot\", botClass: \"scanner\", certain: true, identity: \"nuclei\" },\n }),\n bot({\n id: \"gobuster\",\n title: \"Directory brute-forcing with gobuster\",\n audience: \"hostile\",\n category: \"scanner\",\n provenance: \"Content discovery tool\",\n requests: [{ ...plain(\"gobuster/3.6\"), path: \"/backup\" }],\n expect: { verdict: \"confirmed-bot\", botClass: \"scanner\", certain: true },\n }),\n bot({\n id: \"acunetix\",\n title: \"Acunetix\",\n audience: \"hostile\",\n category: \"scanner\",\n provenance: \"Commercial DAST scanner\",\n requests: [plain(\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 acunetix-product\")],\n expect: { verdict: \"confirmed-bot\", botClass: \"scanner\", certain: true },\n }),\n bot({\n id: \"masscan\",\n title: \"masscan hitting an HTTP port\",\n audience: \"hostile\",\n category: \"scanner\",\n provenance: \"Internet-wide port scanner\",\n requests: [{ headers: [[\"Host\", \"shop.example\"], [\"User-Agent\", \"masscan/1.3 (https://github.com/robertdavidgraham/masscan)\"], [\"Accept\", \"*/*\"]], protocol: \"http\", httpVersion: \"1.1\" }],\n expect: { verdict: \"confirmed-bot\", botClass: \"scanner\", certain: true },\n }),\n bot({\n id: \"censys-scanner\",\n title: \"Censys internet-wide measurement\",\n audience: \"hostile\",\n category: \"scanner\",\n provenance: \"Research scanning that publishes an opt-out address\",\n notes: \"Classified alongside scanners because that is what it is, but it is a research project that honours opt-outs — a good candidate for your allowlist rather than a block.\",\n requests: [plain(\"Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)\")],\n expect: { verdict: \"confirmed-bot\", botClass: \"scanner\", certain: true },\n }),\n bot({\n id: \"internet-measurement\",\n title: \"Anonymous internet measurement\",\n audience: \"hostile\",\n category: \"scanner\",\n provenance: \"Driftnet and similar projects scan the whole address space continuously\",\n requests: [plain(\"Mozilla/5.0 (compatible; InternetMeasurement/1.0; +https://internet-measurement.com/)\")],\n expect: { verdict: \"confirmed-bot\", certain: true },\n }),\n];\n","import { plain } from \"./headers.js\";\nimport { bot } from \"./schema.js\";\nimport type { TrafficCase } from \"./schema.js\";\n\n/**\n * Automation that is honest about itself and that most sites would still rather not\n * serve.\n *\n * SEO and market-intelligence crawlers are run by legitimate businesses doing\n * legitimate work. They also cost you bandwidth to build a product you are not paid\n * for, and at scale a handful of them can outweigh your human traffic. Declining them\n * is a business decision rather than a security one, and the corpus's only assertion\n * is that the library names them precisely enough for a rule to be written.\n *\n * All of them declare themselves, so all of them are `certain`. That matters: a\n * policy that declines these is acting on a statement the crawler made, not on a\n * guess, so it can decline them without any risk to a person.\n */\n\nfunction seo(id: string, title: string, userAgent: string, identity: string, provenance: string, notes?: string): TrafficCase {\n return bot({\n id,\n title,\n audience: \"unwanted-bot\",\n category: \"seo-crawler\",\n provenance,\n ...(notes !== undefined ? { notes } : {}),\n requests: [plain(userAgent)],\n expect: { verdict: \"confirmed-bot\", certain: true, identity, detectors: [\"self-identified\"] },\n });\n}\n\nexport const UNWANTED_BOT_CASES: TrafficCase[] = [\n seo(\"ahrefsbot\", \"AhrefsBot\", \"Mozilla/5.0 (compatible; AhrefsBot/7.0; +http://ahrefs.com/robot/)\", \"ahrefsbot\", \"Backlink index crawler; publishes FCrDNS under ahrefs.com, so it is one of the few here that can actually be verified\"),\n seo(\"ahrefs-site-audit\", \"AhrefsSiteAudit\", \"Mozilla/5.0 (compatible; AhrefsSiteAudit/6.1; +http://ahrefs.com/robot/site-audit)\", \"ahrefsbot\", \"Runs when somebody audits your site — possibly you\"),\n seo(\"semrushbot\", \"SemrushBot\", \"Mozilla/5.0 (compatible; SemrushBot/7~bl; +http://www.semrush.com/bot.html)\", \"semrushbot\", \"Competitive research crawler\"),\n seo(\"mj12bot\", \"MJ12bot\", \"Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)\", \"mj12bot\", \"Majestic's distributed crawler — it runs on volunteers' machines, so it arrives from residential addresses\"),\n seo(\"dotbot\", \"DotBot\", \"Mozilla/5.0 (compatible; DotBot/1.2; +https://opensiteexplorer.org/dotbot; help@moz.com)\", \"dotbot\", \"Feeds Moz's link index and Open Site Explorer\"),\n seo(\"blexbot\", \"BLEXBot\", \"Mozilla/5.0 (compatible; BLEXBot/1.0; +http://webmeup-crawler.com/)\", \"blexbot\", \"Link research crawler feeding a backlink index\"),\n seo(\"dataforseo\", \"DataForSeoBot\", \"Mozilla/5.0 (compatible; DataForSeoBot/1.0; +https://dataforseo.com/dataforseo-bot)\", \"dataforseo\", \"SERP and backlink data as a service\"),\n seo(\"serpstatbot\", \"Serpstatbot\", \"serpstatbot/2.1 (advanced backlink tracking bot; https://serpstatbot.com/; abuse@serpstatbot.com)\", \"serpstatbot\", \"Backlink tracking for an SEO analytics product\"),\n seo(\"barkrowler\", \"Barkrowler\", \"Mozilla/5.0 (compatible; Barkrowler/0.9; +https://babbar.tech/crawler)\", \"barkrowler\", \"Builds a link graph for the Babbar SEO product\"),\n seo(\"zoominfobot\", \"ZoominfoBot\", \"ZoominfoBot (zoominfobot at zoominfo dot com)\", \"zoominfobot\", \"Harvests contact data for a sales product — a crawler many sites decline on privacy grounds rather than cost\"),\n\n bot({\n id: \"seo-crawler-aggressive-rate\",\n title: \"An SEO crawler ignoring Crawl-delay\",\n audience: \"unwanted-bot\",\n category: \"seo-crawler\",\n provenance: \"Declared crawlers sometimes crawl far faster than a site can comfortably serve\",\n notes:\n \"Honest and inconsiderate at once. The right answer is a rate limit, not a block: it identified itself truthfully, and the problem is throughput rather than deception.\",\n requests: Array.from({ length: 40 }, (_, index) => ({\n ...plain(\"Mozilla/5.0 (compatible; SemrushBot/7~bl; +http://www.semrush.com/bot.html)\"),\n path: `/products/${index + 1}`,\n atMs: index * 120,\n })),\n expect: { verdict: \"confirmed-bot\", certain: true, identity: \"semrushbot\" },\n }),\n\n bot({\n id: \"unrecognised-polite-crawler\",\n title: \"A crawler nobody has heard of that follows the conventions\",\n audience: \"declared-bot\",\n category: \"unknown-crawler\",\n provenance: \"New crawlers appear constantly; the convention of naming yourself and publishing a contact URL does not change\",\n notes:\n \"Not in any signature database and correctly handled anyway. Word plus contact address is a declaration, so it reaches `certain` on structure alone — which is what keeps the library useful as the world adds crawlers.\",\n requests: [plain(\"Mozilla/5.0 (compatible; NewIndexBot/0.4; +https://newindex.example/about-our-crawler)\")],\n expect: { verdict: \"confirmed-bot\", botClass: \"declared-bot\", certain: true },\n }),\n\n bot({\n id: \"crawler-word-without-contact\",\n title: \"A crawler-ish name with no contact address\",\n audience: \"unwanted-bot\",\n category: \"unknown-crawler\",\n provenance: \"Half of the convention followed\",\n notes: \"Suggestive, not conclusive. Without the contact address there is no declaration of intent, only a word.\",\n requests: [plain(\"Mozilla/5.0 (compatible; Some Spider 1.0)\")],\n expect: { verdict: \"suspected-bot\", certain: false, botClass: \"declared-bot\", minScore: 55 },\n }),\n];\n","import { BotHandler } from \"../core.js\";\nimport { DENYING_ACTIONS } from \"./schema.js\";\nimport { CORPUS, assertCorpusIntegrity } from \"./index.js\";\nimport { CORPUS_CRAWLER_RANGES } from \"./ranges.js\";\nimport { ManualClock } from \"../internal/clock.js\";\nimport { createFacts } from \"../facts.js\";\nimport type { Assessment, RequestFacts } from \"../types.js\";\nimport type { ActionName, Decision } from \"../policy/types.js\";\nimport type { ActionOutcome } from \"../actions/types.js\";\nimport type { DnsResolver } from \"../internal/dns.js\";\nimport type { Audience, CaseRequest, Expectation, TrafficCase } from \"./schema.js\";\n\n/**\n * The harness.\n *\n * It takes a factory rather than a handler because the corpus tests **your**\n * configuration, not the defaults. Hand it whatever you run in production and it will\n * tell you what that policy does to every shape of traffic in the corpus — including,\n * and this is the part worth reading, which of them are people.\n *\n * Two things it controls that a real deployment does not:\n *\n * - **DNS.** No lookup leaves the process. Each case declares the answers it wants,\n * so the difference between \"the operator's DNS disproves this claim\" and \"our\n * resolver was briefly unhappy\" can actually be tested. Those must reach different\n * verdicts, and only a controlled resolver can prove they do.\n * - **Time.** A `ManualClock` advances by each request's `atMs`, so rate and cadence\n * are exercised deterministically. A corpus that slept would take hours and still\n * be flaky.\n */\n\nexport interface RunnerOptions {\n /** Builds the handler under test. The runner supplies the resolver and the clock. */\n create: (dependencies: { resolver: DnsResolver; clock: ManualClock }) => BotHandler;\n cases?: readonly TrafficCase[];\n /**\n * Load the corpus's fictional crawler ranges into the handler. Default true.\n *\n * Without them the `ip-ranges` verification cases cannot pass, because the library\n * ships no address data and correctly refuses to guess.\n */\n applyCorpusRanges?: boolean;\n /** Epoch ms the first request of every case is stamped with. */\n startedAt?: number;\n /**\n * Capability names the handler under test provides, matched against a case's\n * `requires`. A case naming something absent here is skipped and reported.\n */\n provides?: readonly string[];\n /**\n * Assert each case's `expect.action`. Default true.\n *\n * Turn it off when running the corpus across several policies. Which *action* a\n * case receives is a property of the policy — a case that is allowed under one\n * preset is logged under another and both are correct — whereas the verdict, the\n * certainty and `neverAction` are properties of the traffic and hold everywhere.\n * `neverAction` is always asserted, because it is the invariant, not a preference.\n */\n assertActions?: boolean;\n}\n\nexport interface RequestResult {\n assessment: Assessment;\n decision: Decision;\n outcome: ActionOutcome;\n}\n\nexport interface CaseResult {\n case: TrafficCase;\n /** Every request in the case, in order. */\n requests: RequestResult[];\n /** The request expectations are asserted against — the last one. */\n final: RequestResult;\n /** Human-readable expectation violations. Empty means the case passed. */\n failures: string[];\n /** Set when a case marked `human` was denied service. The failure that matters most. */\n falsePositive: boolean;\n /** Why the case did not run, when it did not. Never counted as a pass. */\n skipped?: string;\n durationMs: number;\n}\n\nexport interface AudienceTally {\n total: number;\n passed: number;\n failed: number;\n actions: Record<string, number>;\n}\n\nexport interface Scorecard {\n results: CaseResult[];\n total: number;\n passed: number;\n failed: number;\n /** Human cases that were denied service. Must be empty. */\n falsePositives: CaseResult[];\n /** Cases the handler under test was not configured to exercise. */\n skipped: CaseResult[];\n /**\n * People whose client software declares itself automated.\n *\n * Exempt from the never-deny rule by construction, and listed here so that the\n * exemption is visible in every report rather than buried in a flag.\n */\n selfDeclaredHumans: CaseResult[];\n byAudience: Record<Audience, AudienceTally>;\n byCategory: Record<string, { total: number; failed: number }>;\n /** How many cases each detector produced evidence on. */\n detectorCoverage: Record<string, number>;\n /**\n * Detectors installed in the handler that no case exercised.\n *\n * A gap in the corpus, not in the library: an untested detector is one whose next\n * regression nobody will notice.\n */\n unexercisedDetectors: string[];\n /** Proven-verdict rate among cases that are genuinely automated. */\n provenAutomation: { total: number; proven: number };\n durationMs: number;\n}\n\nconst DEFAULT_START = Date.UTC(2026, 7, 30, 9, 0, 0);\n\n/**\n * Derives a stable address for a case from its id.\n *\n * Distinct per case, so no case inherits another's behavioural history, and stable\n * across runs, so a failure is reproducible. Drawn from 198.18.0.0/15, which RFC 2544\n * reserves for benchmarking — it cannot collide with anything real, and its meaning is\n * exactly what this is.\n */\nexport function addressFor(id: string): string {\n let hash = 0x811c9dc5;\n for (let i = 0; i < id.length; i++) {\n hash ^= id.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n const value = hash >>> 0;\n return `198.18.${(value >> 8) & 0xff}.${(value & 0xff) || 1}`;\n}\n\n/** Answers from the case's own map. Absent names are NXDOMAIN — a definitive negative. */\nfunction caseResolver(current: () => TrafficCase | undefined): DnsResolver {\n const notFound = (): never => {\n throw Object.assign(new Error(\"not found\"), { code: \"ENOTFOUND\" });\n };\n const unavailable = (): never => {\n throw Object.assign(new Error(\"timed out\"), { code: \"ETIMEOUT\" });\n };\n return {\n async reverse(ip) {\n const dns = current()?.dns;\n if (dns === undefined) return notFound();\n if (dns.unavailable === true) return unavailable();\n const names = dns.reverse?.[ip];\n return names === undefined ? notFound() : [...names];\n },\n async resolveAddresses(hostname) {\n const dns = current()?.dns;\n if (dns === undefined) return notFound();\n if (dns.unavailable === true) return unavailable();\n const addresses = dns.forward?.[hostname];\n return addresses === undefined ? notFound() : [...addresses];\n },\n };\n}\n\nfunction toFacts(request: CaseRequest, fallbackIp: string, timestamp: number): RequestFacts {\n const headers: Record<string, string | string[]> = {};\n const order: string[] = [];\n for (const [name, value] of request.headers) {\n const key = name.toLowerCase();\n const existing = headers[key];\n headers[key] = existing === undefined ? value : Array.isArray(existing) ? [...existing, value] : [existing, value];\n order.push(name);\n }\n\n return createFacts({\n method: request.method ?? \"GET\",\n url: request.path ?? \"/\",\n headers,\n rawHeaders: order,\n ip: request.ip ?? fallbackIp,\n timestamp,\n protocol: request.protocol ?? \"https\",\n httpVersion: request.httpVersion ?? \"1.1\",\n ...(request.tlsFingerprint !== undefined ? { tlsFingerprint: request.tlsFingerprint } : {}),\n ...(request.partialHeaders === true ? { partialHeaders: true } : {}),\n });\n}\n\nfunction asArray<T>(value: T | readonly T[] | undefined): readonly T[] | undefined {\n if (value === undefined) return undefined;\n return Array.isArray(value) ? (value as readonly T[]) : [value as T];\n}\n\nfunction checkExpectations(item: TrafficCase, result: RequestResult, assertActions: boolean): string[] {\n const { assessment, decision, outcome } = result;\n const expect: Expectation = item.expect;\n const failures: string[] = [];\n\n const verdicts = asArray(expect.verdict);\n if (verdicts && !verdicts.includes(assessment.verdict)) {\n failures.push(`verdict was \"${assessment.verdict}\", expected ${verdicts.map((v) => `\"${v}\"`).join(\" or \")}`);\n }\n\n const classes = asArray(expect.botClass);\n if (classes && !classes.includes(assessment.botClass)) {\n failures.push(`botClass was \"${assessment.botClass}\", expected ${classes.map((c) => `\"${c}\"`).join(\" or \")}`);\n }\n\n if (expect.certain !== undefined && assessment.certain !== expect.certain) {\n failures.push(`certain was ${assessment.certain}, expected ${expect.certain}`);\n }\n\n if (expect.identity !== undefined) {\n const identities = new Set([assessment.identity, ...assessment.evidence.map((e) => e.identity)].filter(Boolean));\n if (!identities.has(expect.identity)) {\n failures.push(`identity \"${expect.identity}\" not established (saw ${[...identities].join(\", \") || \"none\"})`);\n }\n }\n\n if (expect.minScore !== undefined && assessment.score < expect.minScore) {\n failures.push(`score was ${assessment.score}, expected at least ${expect.minScore}`);\n }\n if (expect.maxScore !== undefined && assessment.score > expect.maxScore) {\n failures.push(`score was ${assessment.score}, expected at most ${expect.maxScore}`);\n }\n\n const fired = new Set([...assessment.evidence, ...assessment.humanEvidence].map((e) => e.detector));\n for (const detector of expect.detectors ?? []) {\n if (!fired.has(detector)) failures.push(`expected detector \"${detector}\" to fire; it did not (fired: ${[...fired].join(\", \") || \"none\"})`);\n }\n for (const detector of expect.notDetectors ?? []) {\n if (fired.has(detector)) {\n const summary = [...assessment.evidence, ...assessment.humanEvidence].find((e) => e.detector === detector)?.summary ?? \"\";\n failures.push(`detector \"${detector}\" fired but should not have: ${summary}`);\n }\n }\n\n const actions = assertActions ? asArray(expect.action) : undefined;\n if (actions && !actions.includes(decision.action)) {\n failures.push(`action was \"${decision.action}\" (rule \"${decision.rule}\"), expected ${actions.map((a) => `\"${a}\"`).join(\" or \")}`);\n }\n for (const action of expect.neverAction ?? []) {\n if (decision.action === action) failures.push(`action \"${action}\" is forbidden for this case (rule \"${decision.rule}\")`);\n }\n\n if (expect.outcome !== undefined && outcome.kind !== expect.outcome) {\n failures.push(`outcome was \"${outcome.kind}\", expected \"${expect.outcome}\" (decision was \"${decision.action}\")`);\n }\n\n return failures;\n}\n\nexport async function runCase(handler: BotHandler, clock: ManualClock, item: TrafficCase, startedAt: number, provides: ReadonlySet<string>, assertActions = true): Promise<CaseResult> {\n const missing = (item.requires ?? []).filter((capability) => !provides.has(capability));\n if (missing.length > 0) {\n return {\n case: item,\n requests: [],\n final: undefined as unknown as RequestResult,\n failures: [],\n falsePositive: false,\n skipped: `needs configuration this handler does not provide: ${missing.join(\", \")}`,\n durationMs: 0,\n };\n }\n\n const fallbackIp = addressFor(item.id);\n const requests: RequestResult[] = [];\n const began = performance.now();\n\n // Clearance is minted by the handler under test, because only it holds the signing\n // secret. The cookie is bound to the actor, so it is derived from the same facts the\n // case will actually present.\n let clearanceCookie: string | undefined;\n if (item.clearance !== undefined) {\n clock.set(startedAt);\n const seed = toFacts(item.requests[0]!, fallbackIp, clock.now());\n clearanceCookie = handler.grantClearance(seed, item.clearance)?.split(\";\")[0];\n }\n\n for (const request of item.requests) {\n clock.set(startedAt + (request.atMs ?? 0));\n const withClearance: CaseRequest =\n clearanceCookie === undefined ? request : { ...request, headers: [...request.headers, [\"Cookie\", clearanceCookie]] };\n const facts = toFacts(withClearance, fallbackIp, clock.now());\n const { assessment, decision, outcome } = await handler.handle(facts);\n requests.push({ assessment, decision, outcome });\n }\n\n const final = requests[requests.length - 1]!;\n const failures = checkExpectations(item, final, assertActions);\n\n // The audience rule, applied to every request in the case rather than only the\n // last: a person denied on request seven is still a person denied.\n let falsePositive = false;\n // A person whose client declares itself a bot is exempt from the audience rule and\n // counted separately. See `TrafficCase.selfDeclared`.\n if (item.audience === \"human\" && item.selfDeclared === undefined) {\n for (const [index, result] of requests.entries()) {\n if (DENYING_ACTIONS.includes(result.decision.action)) {\n falsePositive = true;\n failures.push(\n `FALSE POSITIVE: request ${index + 1}/${requests.length} from a person was ${result.decision.action}ed by rule \"${result.decision.rule}\" ` +\n `(${result.assessment.verdict}, ${result.assessment.certain ? \"proven\" : `score ${result.assessment.score}`})`,\n );\n }\n }\n }\n\n return { case: item, requests, final, failures, falsePositive, durationMs: performance.now() - began };\n}\n\nconst AUDIENCES: readonly Audience[] = [\"human\", \"benign-bot\", \"declared-bot\", \"unwanted-bot\", \"hostile\", \"infrastructure\"];\n\nexport async function runCorpus(options: RunnerOptions): Promise<Scorecard> {\n const cases = options.cases ?? CORPUS;\n assertCorpusIntegrity(cases);\n\n const startedAt = options.startedAt ?? DEFAULT_START;\n const clock = new ManualClock(startedAt);\n let current: TrafficCase | undefined;\n const handler = options.create({ resolver: caseResolver(() => current), clock });\n\n if (options.applyCorpusRanges !== false) {\n for (const [id, entries] of Object.entries(CORPUS_CRAWLER_RANGES)) handler.updateCrawlerRanges(id, entries);\n }\n\n const provides = new Set(options.provides ?? []);\n // Loading the ranges *is* providing the capability, so cases that depend on them do\n // not have to be listed by every caller. A harness that switches the ranges off\n // correctly skips those cases instead of failing them.\n if (options.applyCorpusRanges !== false) provides.add(\"crawler-ranges\");\n const began = performance.now();\n const results: CaseResult[] = [];\n for (const item of cases) {\n current = item;\n results.push(await runCase(handler, clock, item, startedAt, provides, options.assertActions !== false));\n }\n current = undefined;\n\n const byAudience = Object.fromEntries(AUDIENCES.map((audience) => [audience, { total: 0, passed: 0, failed: 0, actions: {} as Record<string, number> }])) as Record<Audience, AudienceTally>;\n const byCategory: Record<string, { total: number; failed: number }> = {};\n const detectorCoverage: Record<string, number> = {};\n let passed = 0;\n let provenTotal = 0;\n let proven = 0;\n\n for (const result of results) {\n if (result.skipped !== undefined) continue;\n const tally = byAudience[result.case.audience];\n tally.total++;\n const action: ActionName = result.final.decision.action;\n tally.actions[action] = (tally.actions[action] ?? 0) + 1;\n\n const category = (byCategory[result.case.category] ??= { total: 0, failed: 0 });\n category.total++;\n\n if (result.failures.length === 0) {\n passed++;\n tally.passed++;\n } else {\n tally.failed++;\n category.failed++;\n }\n\n const fired = new Set<string>();\n for (const request of result.requests) {\n for (const item of [...request.assessment.evidence, ...request.assessment.humanEvidence]) fired.add(item.detector);\n }\n for (const detector of fired) detectorCoverage[detector] = (detectorCoverage[detector] ?? 0) + 1;\n\n if (result.case.audience !== \"human\" && result.case.audience !== \"infrastructure\") {\n provenTotal++;\n if (result.final.assessment.certain) proven++;\n }\n }\n\n const installed = handler.describeDetectors().map((entry) => entry.id);\n const unexercisedDetectors = installed.filter((id) => detectorCoverage[id] === undefined);\n\n const skipped = results.filter((result) => result.skipped !== undefined);\n\n return {\n results,\n total: results.length - skipped.length,\n passed,\n failed: results.length - skipped.length - passed,\n falsePositives: results.filter((result) => result.falsePositive),\n selfDeclaredHumans: results.filter((result) => result.case.audience === \"human\" && result.case.selfDeclared !== undefined),\n skipped,\n byAudience,\n byCategory,\n detectorCoverage,\n unexercisedDetectors,\n provenAutomation: { total: provenTotal, proven },\n durationMs: performance.now() - began,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAAA,IAYa;AAZb;AAAA;AAAA;AAYO,IAAM,cAAN,MAAmC;AAAA,MACxC,YAAoB,UAAU,GAAG;AAAb;AAAA,MAAc;AAAA,MAAd;AAAA,MACpB,MAAc;AACZ,eAAO,KAAK;AAAA,MACd;AAAA,MACA,QAAQ,IAAkB;AACxB,aAAK,WAAW;AAAA,MAClB;AAAA,MACA,IAAI,IAAkB;AACpB,aAAK,UAAU;AAAA,MACjB;AAAA,IACF;AAAA;AAAA;;;ACTO,SAAS,aAAa,QAAoD;AAC/E,QAAM,UAAkC,uBAAO,OAAO,IAAI;AAC1D,MAAI,WAAW,UAAa,OAAO,WAAW,EAAG,QAAO;AACxD,QAAM,SAAS,OAAO,SAAS,oBAAoB,OAAO,MAAM,GAAG,iBAAiB,IAAI;AAExF,MAAI,QAAQ;AACZ,aAAW,QAAQ,OAAO,MAAM,GAAG,GAAG;AACpC,QAAI,SAAS,YAAa;AAC1B,UAAM,SAAS,KAAK,QAAQ,GAAG;AAC/B,QAAI,UAAU,EAAG;AACjB,UAAM,OAAO,KAAK,MAAM,GAAG,MAAM,EAAE,KAAK;AACxC,QAAI,KAAK,WAAW,EAAG;AACvB,UAAM,WAAW,KAAK,MAAM,SAAS,CAAC,EAAE,KAAK;AAC7C,UAAM,QAAQ,SAAS,WAAW,GAAG,KAAK,SAAS,SAAS,GAAG,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI;AAC3F,QAAI;AACF,cAAQ,IAAI,IAAI,mBAAmB,KAAK;AAAA,IAC1C,QAAQ;AAEN,cAAQ,IAAI,IAAI;AAAA,IAClB;AACA;AAAA,EACF;AACA,SAAO;AACT;AAkCO,SAAS,gBAAgB,OAA0D;AACxF,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI;AACnD;AA1EA,IAGM,mBACA;AAJN;AAAA;AAAA;AAGA,IAAM,oBAAoB;AAC1B,IAAM,cAAc;AAAA;AAAA;;;ACab,SAAS,QAAQ,OAA+B;AACrD,QAAM,QAAQ,MAAM,KAAK;AACzB,MAAI,MAAM,WAAW,KAAK,MAAM,SAAS,GAAI,QAAO;AAEpD,QAAM,OAAO,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,IAAI,MAAM,MAAM,GAAG,EAAE,IAAI;AACjF,MAAI,KAAK,SAAS,GAAG,EAAG,QAAO,UAAU,IAAI;AAC7C,SAAO,UAAU,IAAI;AACvB;AAEA,SAAS,UAAU,OAA+B;AAChD,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,QAAQ,IAAI,WAAW,CAAC;AAC9B,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,OAAO,MAAM,CAAC;AAEpB,QAAI,KAAK,WAAW,KAAK,KAAK,SAAS,EAAG,QAAO;AACjD,QAAI,CAAC,QAAQ,KAAK,IAAI,EAAG,QAAO;AAChC,QAAI,KAAK,SAAS,KAAK,KAAK,CAAC,MAAM,IAAK,QAAO;AAC/C,UAAM,IAAI,OAAO,IAAI;AACrB,QAAI,IAAI,IAAK,QAAO;AACpB,UAAM,CAAC,IAAI;AAAA,EACb;AACA,SAAO;AACT;AAEA,SAAS,UAAU,OAA+B;AAEhD,QAAM,cAAc,MAAM,MAAM,GAAG,EAAE,CAAC;AACtC,QAAM,cAAc,YAAY,QAAQ,IAAI;AAC5C,MAAI,gBAAgB,YAAY,YAAY,IAAI,EAAG,QAAO;AAE1D,QAAM,CAAC,UAAU,QAAQ,IACvB,gBAAgB,KACZ,CAAC,aAAa,EAAE,IAChB,CAAC,YAAY,MAAM,GAAG,WAAW,GAAG,YAAY,MAAM,cAAc,CAAC,CAAC;AAE5E,QAAM,OAAO,SAAS,SAAS,IAAI,SAAS,MAAM,GAAG,IAAI,CAAC;AAC1D,QAAM,OAAO,SAAS,SAAS,IAAI,SAAS,MAAM,GAAG,IAAI,CAAC;AAG1D,MAAI,WAA2B;AAC/B,QAAM,SAAS,CAAC,GAAG,MAAM,GAAG,IAAI;AAChC,QAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,MAAI,SAAS,UAAa,KAAK,SAAS,GAAG,GAAG;AAC5C,eAAW,UAAU,IAAI;AACzB,QAAI,CAAC,SAAU,QAAO;AACtB,QAAI,KAAK,SAAS,EAAG,MAAK,IAAI;AAAA,QACzB,MAAK,IAAI;AAAA,EAChB;AAEA,QAAM,aAAa,KAAK,SAAS,KAAK,UAAU,WAAW,IAAI;AAC/D,MAAI,gBAAgB,KAAK,eAAe,IAAI,aAAa,EAAG,QAAO;AAEnE,QAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,MAAI,SAAS;AACb,aAAW,SAAS,MAAM;AACxB,QAAI,CAAC,WAAW,OAAO,QAAQ,KAAK,EAAG,QAAO;AAC9C,cAAU;AAAA,EACZ;AAEA,WAAS,KAAK,KAAK,SAAS,KAAK,WAAW,IAAI;AAChD,aAAW,SAAS,MAAM;AACxB,QAAI,CAAC,WAAW,OAAO,QAAQ,KAAK,EAAG,QAAO;AAC9C,cAAU;AAAA,EACZ;AACA,MAAI,SAAU,OAAM,IAAI,UAAU,EAAE;AAIpC,MAAI,aAAa,KAAK,EAAG,QAAO,MAAM,MAAM,IAAI,EAAE;AAClD,SAAO;AACT;AAEA,SAAS,WAAW,OAAmB,QAAgB,OAAwB;AAC7E,MAAI,MAAM,WAAW,KAAK,MAAM,SAAS,KAAK,CAAC,iBAAiB,KAAK,KAAK,EAAG,QAAO;AACpF,QAAM,IAAI,OAAO,SAAS,OAAO,EAAE;AACnC,QAAM,MAAM,IAAI,KAAK;AACrB,QAAM,SAAS,CAAC,IAAI,IAAI;AACxB,SAAO;AACT;AAEA,SAAS,aAAa,OAA4B;AAChD,WAAS,IAAI,GAAG,IAAI,IAAI,IAAK,KAAI,MAAM,CAAC,MAAM,EAAG,QAAO;AACxD,SAAO,MAAM,EAAE,MAAM,OAAQ,MAAM,EAAE,MAAM;AAC7C;AAGO,SAAS,SAAS,OAAwB;AAC/C,MAAI,MAAM,WAAW,EAAG,QAAO,GAAG,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AAC9E,QAAM,SAAmB,CAAC;AAC1B,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK,EAAG,QAAO,MAAO,MAAM,CAAC,KAAM,IAAK,MAAM,IAAI,CAAC,GAAI,SAAS,EAAE,CAAC;AAE3F,MAAI,YAAY;AAChB,MAAI,UAAU;AACd,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,QAAI,OAAO,CAAC,MAAM,IAAK;AACvB,QAAI,IAAI;AACR,WAAO,IAAI,KAAK,OAAO,CAAC,MAAM,IAAK;AACnC,QAAI,IAAI,IAAI,SAAS;AACnB,gBAAU,IAAI;AACd,kBAAY;AAAA,IACd;AACA,QAAI;AAAA,EACN;AACA,MAAI,UAAU,EAAG,QAAO,OAAO,KAAK,GAAG;AACvC,SAAO,GAAG,OAAO,MAAM,GAAG,SAAS,EAAE,KAAK,GAAG,CAAC,KAAK,OAAO,MAAM,YAAY,OAAO,EAAE,KAAK,GAAG,CAAC;AAChG;AAGO,SAAS,YAAY,OAA8B;AACxD,QAAM,QAAQ,QAAQ,KAAK;AAC3B,SAAO,QAAQ,SAAS,KAAK,IAAI;AACnC;AASO,SAAS,UAAU,OAA4B;AACpD,QAAM,QAAQ,MAAM,YAAY,GAAG;AACnC,QAAM,cAAc,UAAU,KAAK,QAAQ,MAAM,MAAM,GAAG,KAAK;AAC/D,QAAM,QAAQ,QAAQ,WAAW;AACjC,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,YAAY,MAAM,SAAS;AACjC,MAAI,UAAU,GAAI,QAAO,EAAE,OAAO,QAAQ,WAAW,QAAQ,MAAM;AACnE,QAAM,aAAa,MAAM,MAAM,QAAQ,CAAC;AACxC,MAAI,CAAC,YAAY,KAAK,UAAU,EAAG,QAAO;AAC1C,QAAM,SAAS,OAAO,UAAU;AAChC,MAAI,SAAS,UAAW,QAAO;AAC/B,SAAO,EAAE,OAAO,QAAQ,QAAQ,MAAM;AACxC;AAGO,SAAS,aAAa,MAAY,IAAsB;AAC7D,MAAI,KAAK,MAAM,WAAW,GAAG,OAAQ,QAAO;AAC5C,QAAM,YAAY,KAAK,UAAU;AACjC,WAAS,IAAI,GAAG,IAAI,WAAW,IAAK,KAAI,KAAK,MAAM,CAAC,MAAM,GAAG,CAAC,EAAG,QAAO;AACxE,QAAM,YAAY,KAAK,SAAS;AAChC,MAAI,cAAc,EAAG,QAAO;AAC5B,QAAM,OAAO,OAAS,IAAI;AAC1B,UAAQ,KAAK,MAAM,SAAS,IAAK,WAAW,GAAG,SAAS,IAAK;AAC/D;AAsEA,SAAS,KAAK,SAAiC,IAAa,MAAwD;AAClH,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,QAAQ,QAAQ,CAAC;AACvB,QAAI,WAAW,UAAa,MAAM,QAAQ,OAAO,MAAO;AACxD,QAAI,aAAa,MAAM,MAAM,EAAE,EAAG,UAAS;AAAA,EAC7C;AACA,SAAO;AACT;AAhPA,IA6LM,aA0DO,YA2DA,oBAqBP;AAvUN;AAAA;AAAA;AA6LA,IAAM,cAAN,MAAkB;AAAA;AAAA,MAEC,OAAsB,CAAC;AAAA,MACvB,UAAU,oBAAI,IAAsE;AAAA,MAErG,IAAI,OAA0B;AAC5B,cAAM,EAAE,KAAK,IAAI;AACjB,YAAI,KAAK,SAAS,GAAG;AACnB,eAAK,KAAK,KAAK,KAAK;AACpB;AAAA,QACF;AACA,cAAM,QAAQ,KAAK,MAAM,CAAC;AAC1B,YAAI,SAAS,KAAK,QAAQ,IAAI,KAAK;AACnC,YAAI,WAAW,QAAW;AACxB,mBAAS,EAAE,KAAK,CAAC,GAAG,MAAM,oBAAI,IAAI,EAAE;AACpC,eAAK,QAAQ,IAAI,OAAO,MAAM;AAAA,QAChC;AACA,YAAI,KAAK,SAAS,IAAI;AACpB,iBAAO,IAAI,KAAK,KAAK;AACrB;AAAA,QACF;AACA,cAAM,SAAS,KAAK,MAAM,CAAC;AAC3B,YAAI,OAAO,OAAO,KAAK,IAAI,MAAM;AACjC,YAAI,SAAS,QAAW;AACtB,iBAAO,CAAC;AACR,iBAAO,KAAK,IAAI,QAAQ,IAAI;AAAA,QAC9B;AACA,aAAK,KAAK,KAAK;AAAA,MACjB;AAAA;AAAA,MAGA,KAAK,IAAsC;AACzC,YAAI,OAAO,KAAK,KAAK,MAAM,IAAI,MAAS;AACxC,cAAM,SAAS,KAAK,QAAQ,IAAI,GAAG,CAAC,CAAE;AACtC,YAAI,WAAW,QAAW;AACxB,iBAAO,KAAK,OAAO,KAAK,IAAI,IAAI;AAChC,gBAAM,OAAO,OAAO,KAAK,IAAI,GAAG,CAAC,CAAE;AACnC,cAAI,SAAS,OAAW,QAAO,KAAK,MAAM,IAAI,IAAI;AAAA,QACpD;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAiBO,IAAM,aAAN,MAAiB;AAAA,MACL,KAAK,IAAI,YAAY;AAAA,MACrB,KAAK,IAAI,YAAY;AAAA,MAC9B,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAUC,UAAoB,CAAC;AAAA;AAAA,MAE7B,UAAoB,CAAC;AAAA,MAE9B,YAAY,SAA2B,CAAC,GAAG;AACzC,mBAAW,SAAS,OAAQ,MAAK,IAAI,KAAK;AAAA,MAC5C;AAAA,MAEA,IAAI,OAAqB;AACvB,cAAM,OAAO,UAAU,KAAK;AAC5B,YAAI,CAAC,MAAM;AACT,eAAK,QAAQ,KAAK,KAAK;AACvB;AAAA,QACF;AACA,aAAK,QAAQ,KAAK,KAAK,MAAM;AAC7B,SAAC,KAAK,MAAM,WAAW,IAAI,KAAK,KAAK,KAAK,IAAI,IAAI,EAAE,MAAM,OAAO,KAAK,QAAQ,CAAC;AAAA,MACjF;AAAA,MAEA,IAAI,OAAe;AACjB,eAAO,KAAK;AAAA,MACd;AAAA;AAAA,MAGA,UAA6B;AAC3B,eAAO,KAAK;AAAA,MACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,MAAM,IAA0C;AAC9C,cAAM,QAAQ,OAAO,OAAO,WAAW,QAAQ,EAAE,IAAI;AACrD,YAAI,CAAC,MAAO,QAAO;AACnB,gBAAQ,MAAM,WAAW,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,GAAG,KAAK;AAAA,MACpE;AAAA,MAEA,SAAS,IAA+B;AACtC,cAAM,QAAQ,OAAO,OAAO,WAAW,QAAQ,EAAE,IAAI;AACrD,YAAI,CAAC,MAAO,QAAO;AACnB,gBAAQ,MAAM,WAAW,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM;AAAA,MAClE;AAAA,IACF;AAGO,IAAM,qBAAqB;AAAA,MAChC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,IAAM,cAAc,IAAI,WAAW,kBAAkB;AAAA;AAAA;;;ACjR9C,SAAS,YAAY,OAAiC;AAC3D,QAAM,SAAS,MAAM,OAAO;AAC5B,QAAM,MAAM,OAAO,SAAS,iBAAiB,OAAO,MAAM,GAAG,cAAc,IAAI;AAC/E,QAAM,aAAa,IAAI,QAAQ,GAAG;AAClC,QAAM,UAAU,eAAe,KAAK,MAAM,IAAI,MAAM,GAAG,UAAU;AAEjE,QAAM,UAA8C,uBAAO,OAAO,IAAI;AACtE,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,MAAM,OAAO,GAAG;AACzD,UAAM,SAAS,gBAAgB,KAAK;AACpC,QAAI,WAAW,OAAW,SAAQ,KAAK,YAAY,CAAC,IAAI;AAAA,EAC1D;AAEA,QAAM,QAAsB;AAAA,IAC1B,SAAS,MAAM,UAAU,OAAO,YAAY;AAAA,IAC5C,MAAM,cAAc,OAAO;AAAA,IAC3B,OAAO,WAAW,eAAe,KAAK,KAAK,IAAI,MAAM,aAAa,CAAC,CAAC;AAAA,IACpE;AAAA,IACA,aAAa,aAAa,MAAM,YAAY,OAAO;AAAA,IACnD,IAAI,YAAY,MAAM,EAAE,KAAK,MAAM;AAAA,IACnC,WAAW,MAAM,aAAa,KAAK,IAAI;AAAA,EACzC;AAIA,QAAM,eAAe,QAAQ,QAAQ;AACrC,MAAI,iBAAiB,OAAW,OAAM,UAAU,aAAa,YAAY;AAEzE,MAAI,MAAM,aAAa,OAAW,OAAM,WAAW,MAAM;AACzD,MAAI,MAAM,gBAAgB,OAAW,OAAM,cAAc,MAAM;AAC/D,MAAI,MAAM,mBAAmB,OAAW,OAAM,iBAAiB,MAAM;AACrE,MAAI,MAAM,mBAAmB,KAAM,OAAM,iBAAiB;AAC1D,MAAI,MAAM,UAAU,OAAW,OAAM,QAAQ,MAAM;AAEnD,SAAO;AACT;AAWA,SAAS,cAAc,SAAyB;AAC9C,MAAI,OAAO;AACX,MAAI;AACF,WAAO,mBAAmB,OAAO;AAAA,EACnC,QAAQ;AAAA,EAGR;AACA,SAAO,KAAK,QAAQ,OAAO,GAAG,EAAE,QAAQ,WAAW,GAAG;AACtD,MAAI,CAAC,KAAK,WAAW,GAAG,EAAG,QAAO,IAAI,IAAI;AAG1C,MAAI,KAAK,SAAS,IAAI,GAAG;AACvB,UAAM,WAAqB,CAAC;AAC5B,eAAW,WAAW,KAAK,MAAM,GAAG,GAAG;AACrC,UAAI,YAAY,MAAM,YAAY,IAAK;AACvC,UAAI,YAAY,KAAM,UAAS,IAAI;AAAA,UAC9B,UAAS,KAAK,OAAO;AAAA,IAC5B;AACA,WAAO,IAAI,SAAS,KAAK,GAAG,CAAC;AAAA,EAC/B;AACA,SAAO,KAAK,SAAS,KAAK,KAAK,SAAS,GAAG,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI;AACrE;AAEA,SAAS,WAAW,QAAwC;AAG1D,QAAM,QAAgC,uBAAO,OAAO,IAAI;AACxD,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,MAAI,QAAQ;AACZ,aAAW,CAAC,KAAK,KAAK,KAAK,IAAI,gBAAgB,MAAM,GAAG;AACtD,QAAI,WAAW,iBAAkB;AACjC,UAAM,GAAG,IAAI,MAAM,SAAS,OAAO,MAAM,MAAM,GAAG,IAAI,IAAI;AAAA,EAC5D;AACA,SAAO;AACT;AAqBA,SAAS,aAAa,YAA2C,SAAgE;AAC/H,MAAI,CAAC,cAAc,WAAW,WAAW,EAAG,QAAO;AAEnD,MAAI,cAAc,WAAW,SAAS,MAAM;AAC5C,MAAI,aAAa;AACf,QAAI,sBAAsB;AAC1B,aAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,YAAM,QAAQ,WAAW,CAAC;AAC1B,YAAM,QAAQ,QAAQ,MAAM,YAAY,CAAC,MAAM;AAI/C,UAAI,IAAI,MAAM,KAAK,CAAC,SAAS,CAAC,aAAa,KAAK,GAAG;AACjD,sBAAc;AACd;AAAA,MACF;AACA,UAAI,CAAC,MAAO,uBAAsB;AAAA,IACpC;AACA,QAAI,oBAAqB,eAAc;AAAA,EACzC;AAIA,QAAM,OAAO,cAAc,IAAI;AAC/B,QAAM,QAAkB,CAAC;AACzB,WAAS,IAAI,GAAG,IAAI,WAAW,UAAU,MAAM,SAAS,qBAAqB,KAAK,MAAM;AACtF,UAAM,KAAK,WAAW,CAAC,EAAG,YAAY,CAAC;AAAA,EACzC;AACA,SAAO;AACT;AAKA,SAAS,aAAa,OAAwB;AAC5C,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,OAAO,MAAM,WAAW,CAAC;AAC/B,UAAM,KACH,QAAQ,MAAQ,QAAQ;AAAA,IACxB,QAAQ,MAAQ,QAAQ;AAAA,IACxB,QAAQ,MAAQ,QAAQ;AAAA,IACzB,SAAS,MAAS,QAAQ,MAAQ,QAAQ,MAAS,SAAS,MAAQ,SAAS,MAC7E,SAAS,MAAQ,SAAS,MAAQ,SAAS,MAAQ,SAAS,MAAQ,SAAS,MAC7E,SAAS,OAAQ,SAAS;AAC5B,QAAI,CAAC,GAAI,QAAO;AAAA,EAClB;AACA,SAAO;AACT;AA3MA,IAKM,gBAEA,kBAkJA,qBAiCA;AA1LN;AAAA;AAAA;AAAA;AACA;AAIA,IAAM,iBAAiB;AAEvB,IAAM,mBAAmB;AAkJzB,IAAM,sBAAsB;AAiC5B,IAAM,cAAiC,OAAO,OAAO,CAAC,CAAC;AAAA;AAAA;;;AC1LvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACmGA,IAAM,OAAO;AAcb,SAAS,cAAc,MAA6B;AAClD,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,CAAC,CAAC,kBAAkB,MAAM,GAAG,CAAC,kBAAkB,UAAU,GAAG,CAAC,kBAAkB,IAAI,GAAG,CAAC,kBAAkB,UAAU,CAAC;AAAA,IAC9H,KAAK;AACH,aAAO,CAAC,CAAC,kBAAkB,aAAa,GAAG,CAAC,kBAAkB,UAAU,GAAG,CAAC,kBAAkB,IAAI,GAAG,CAAC,kBAAkB,UAAU,CAAC;AAAA,IACrI,KAAK;AACH,aAAO,CAAC,CAAC,kBAAkB,YAAY,GAAG,CAAC,kBAAkB,UAAU,GAAG,CAAC,kBAAkB,IAAI,GAAG,CAAC,kBAAkB,UAAU,CAAC;AAAA,IACpI,KAAK;AACH,aAAO,CAAC,CAAC,kBAAkB,aAAa,GAAG,CAAC,kBAAkB,UAAU,GAAG,CAAC,kBAAkB,IAAI,GAAG,CAAC,kBAAkB,UAAU,CAAC;AAAA,IACrI,KAAK;AACH,aAAO,CAAC,CAAC,kBAAkB,aAAa,GAAG,CAAC,kBAAkB,MAAM,GAAG,CAAC,kBAAkB,OAAO,CAAC;AAAA,IACpG,KAAK;AACH,aAAO,CAAC,CAAC,kBAAkB,YAAY,GAAG,CAAC,kBAAkB,MAAM,GAAG,CAAC,kBAAkB,OAAO,CAAC;AAAA,IACnG,KAAK;AACH,aAAO,CAAC,CAAC,kBAAkB,aAAa,GAAG,CAAC,kBAAkB,SAAS,GAAG,CAAC,kBAAkB,OAAO,CAAC;AAAA,IACvG,KAAK;AACH,aAAO,CAAC,CAAC,kBAAkB,aAAa,GAAG,CAAC,kBAAkB,SAAS,GAAG,CAAC,kBAAkB,OAAO,CAAC;AAAA,IACvG,KAAK;AACH,aAAO,CAAC,CAAC,kBAAkB,aAAa,GAAG,CAAC,kBAAkB,SAAS,GAAG,CAAC,kBAAkB,QAAQ,CAAC;AAAA,IACxG,KAAK;AACH,aAAO,CAAC,CAAC,kBAAkB,aAAa,GAAG,CAAC,kBAAkB,SAAS,GAAG,CAAC,kBAAkB,OAAO,CAAC;AAAA,IACvG,KAAK;AACH,aAAO,CAAC,CAAC,kBAAkB,aAAa,GAAG,CAAC,kBAAkB,MAAM,GAAG,CAAC,kBAAkB,OAAO,CAAC;AAAA,IACpG,KAAK;AACH,aAAO,CAAC,CAAC,kBAAkB,YAAY,GAAG,CAAC,kBAAkB,UAAU,GAAG,CAAC,kBAAkB,QAAQ,CAAC;AAAA,EAC1G;AACF;AAEA,SAAS,UAAU,MAAmB,gBAAgC;AACpE,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAEA,IAAM,eAAe,CAAC,SAA+B,KAAK,SAAS,UAAU,KAAK,SAAS;AAG3F,SAAS,YAAY,SAA+D;AAClF,QAAM,QAAkB,CAAC;AACzB,QAAM,OAAiB,CAAC;AAExB,MAAI,QAAQ,WAAW,KAAM,OAAM,KAAK,CAAC,iBAAiB,WAAW,CAAC;AACtE,MAAI,QAAQ,cAAc,KAAM,OAAM,KAAK,CAAC,cAAc,GAAG,CAAC;AAC9D,MAAI,QAAQ,YAAY,SAAS,OAAW,OAAM,KAAK,CAAC,iBAAiB,QAAQ,WAAW,IAAI,CAAC;AACjG,MAAI,QAAQ,YAAY,kBAAkB,OAAW,OAAM,KAAK,CAAC,qBAAqB,QAAQ,WAAW,aAAa,CAAC;AACvH,MAAI,QAAQ,UAAU,OAAW,OAAM,KAAK,CAAC,SAAS,QAAQ,KAAK,CAAC;AAEpE,MAAI,QAAQ,QAAQ,KAAM,MAAK,KAAK,CAAC,OAAO,GAAG,CAAC;AAChD,MAAI,QAAQ,QAAQ,KAAM,MAAK,KAAK,CAAC,WAAW,GAAG,CAAC;AACpD,MAAI,QAAQ,aAAa,KAAM,MAAK,KAAK,CAAC,aAAa,IAAI,CAAC;AAC5D,MAAI,QAAQ,cAAc;AACxB,SAAK,KAAK,CAAC,OAAO,OAAO,QAAQ,aAAa,GAAG,CAAC,GAAG,CAAC,YAAY,OAAO,QAAQ,aAAa,QAAQ,CAAC,GAAG,CAAC,OAAO,QAAQ,aAAa,GAAG,CAAC;AAAA,EAC7I;AACA,MAAI,QAAQ,aAAa;AACvB,SAAK,KAAK,CAAC,kBAAkB,OAAO,QAAQ,YAAY,aAAa,CAAC,GAAG,CAAC,OAAO,OAAO,QAAQ,YAAY,GAAG,CAAC,CAAC;AAAA,EACnH;AACA,MAAI,QAAQ,aAAa,KAAM,MAAK,KAAK,CAAC,eAAe,UAAU,GAAG,CAAC,WAAW,UAAU,CAAC;AAE7F,SAAO,EAAE,OAAO,KAAK;AACvB;AAEA,SAAS,KAAK,SAAoC;AAChD,MAAI,QAAQ,gBAAgB,OAAW,QAAO,CAAC;AAC/C,QAAM,UAAoB,CAAC,CAAC,gBAAgB,QAAQ,WAAW,CAAC;AAChE,MAAI,QAAQ,kBAAkB,OAAW,SAAQ,KAAK,CAAC,kBAAkB,OAAO,QAAQ,aAAa,CAAC,CAAC;AACvG,SAAO;AACT;AAqBA,SAAS,cAAc,QAAwB;AAC7C,SAAO,CAAC,SAA0B,SAAkC;AAClE,UAAM,MAAM,aAAa,QAAQ,IAAI;AACrC,UAAM,EAAE,OAAO,KAAK,IAAI,YAAY,OAAO;AAC3C,UAAM,QAAkB;AAAA,MACtB,CAAC,aAAa,OAAO,MAAM;AAAA,MAC3B,CAAC,oBAAoB,OAAO,SAAS,OAAO,IAAI;AAAA,MAChD,CAAC,sBAAsB,IAAI,OAAO,QAAQ,GAAG;AAAA,IAC/C;AACA,QAAI,QAAQ,qBAAqB,MAAM;AACrC,YAAM;AAAA,QACJ,CAAC,+BAA+B,OAAO,YAAY;AAAA,QACnD,CAAC,8BAA8B,IAAI,OAAO,eAAe,GAAG;AAAA,QAC5D,CAAC,kBAAkB,IAAI,OAAO,SAAS,OAAO,SAAS,QAAQ,MAAM,GAAG;AAAA,QACxE,CAAC,qBAAqB,IAAI,OAAO,WAAW,IAAI,GAAG;AAAA,QACnD,CAAC,mBAAmB,IAAI,OAAO,SAAS,EAAE,GAAG;AAAA,MAC/C;AAAA,IACF;AACA,QAAI,QAAQ,SAAS,gBAAgB,OAAW,OAAM,KAAK,CAAC,+BAA+B,QAAQ,QAAQ,WAAW,CAAC;AACvH,QAAI,QAAQ,SAAS,kBAAkB,OAAW,OAAM,KAAK,CAAC,iCAAiC,QAAQ,QAAQ,aAAa,CAAC;AAE7H,WAAO;AAAA,MACL,CAAC,QAAQ,QAAQ,IAAI;AAAA,MACrB,CAAC,cAAc,YAAY;AAAA,MAC3B,GAAG;AAAA,MACH,GAAG;AAAA,MACH,GAAI,MAAO,CAAC,CAAC,6BAA6B,GAAG,CAAC,IAAiB,CAAC;AAAA,MAChE,CAAC,cAAc,KAAK,SAAS;AAAA,MAC7B,GAAI,QAAQ,WAAW,SAAa,CAAC,CAAC,UAAU,QAAQ,MAAM,CAAC,IAAiB,CAAC;AAAA,MACjF,GAAG,KAAK,OAAO;AAAA,MACf,CAAC,UAAU,UAAU,QAAQ,MAAM,KAAK,cAAc,CAAC;AAAA,MACvD,GAAG,cAAc,QAAQ,IAAI;AAAA,MAC7B,GAAI,QAAQ,kBAAkB,SAAa,CAAC,CAAC,4BAA4B,QAAQ,aAAa,CAAC,IAAiB,CAAC;AAAA,MACjH,GAAI,QAAQ,YAAY,SAAa,CAAC,CAAC,WAAW,QAAQ,OAAO,CAAC,IAAiB,CAAC;AAAA,MACpF,CAAC,mBAAmB,KAAK,cAAc;AAAA,MACvC,CAAC,mBAAmB,QAAQ,cAAc;AAAA,MAC1C,GAAG;AAAA,MACH,CAAC,YAAY,MAAM,WAAW,QAAQ;AAAA,MACtC,GAAI,QAAQ,WAAW,SAAa,CAAC,CAAC,UAAU,QAAQ,MAAM,CAAC,IAAiB,CAAC;AAAA,IACnF;AAAA,EACF;AACF;AAKA,SAAS,WAAW,SAA0B,MAA+B;AAC3E,QAAM,MAAM,aAAa,QAAQ,IAAI;AACrC,QAAM,EAAE,OAAO,KAAK,IAAI,YAAY,OAAO;AAC3C,SAAO;AAAA,IACL,CAAC,QAAQ,QAAQ,IAAI;AAAA,IACrB,CAAC,cAAc,KAAK,SAAS;AAAA,IAC7B,CAAC,UAAU,UAAU,QAAQ,MAAM,KAAK,cAAc,CAAC;AAAA,IACvD,CAAC,mBAAmB,QAAQ,cAAc;AAAA,IAC1C,CAAC,mBAAmB,KAAK,cAAc;AAAA,IACvC,GAAI,QAAQ,YAAY,SAAa,CAAC,CAAC,WAAW,QAAQ,OAAO,CAAC,IAAiB,CAAC;AAAA,IACpF,GAAI,QAAQ,WAAW,SAAa,CAAC,CAAC,UAAU,QAAQ,MAAM,CAAC,IAAiB,CAAC;AAAA,IACjF,GAAG,KAAK,OAAO;AAAA,IACf,GAAG;AAAA,IACH,CAAC,cAAc,YAAY;AAAA,IAC3B,GAAI,QAAQ,WAAW,SAAa,CAAC,CAAC,UAAU,QAAQ,MAAM,CAAC,IAAiB,CAAC;AAAA,IACjF,GAAG;AAAA,IACH,GAAI,MAAO,CAAC,CAAC,6BAA6B,GAAG,CAAC,IAAiB,CAAC;AAAA,IAChE,GAAG,gBAAgB,cAAc,QAAQ,IAAI,CAAC;AAAA,IAC9C,CAAC,YAAY,MAAM,WAAW,KAAK;AAAA,IACnC,CAAC,MAAM,UAAU;AAAA,EACnB;AACF;AAGA,SAAS,gBAAgB,SAA6B;AACpD,QAAM,QAAQ,CAAC,kBAAkB,kBAAkB,kBAAkB,gBAAgB;AACrF,SAAO,MAAM,QAAQ,CAAC,SAAS,QAAQ,OAAO,CAAC,CAAC,MAAM,MAAM,WAAW,IAAI,CAAC;AAC9E;AAKA,SAAS,YAAY,SAA0B,MAA+B;AAC5E,QAAM,WAAW,IAAI,IAAI,cAAc,QAAQ,IAAI,CAAC;AACpD,QAAM,EAAE,OAAO,KAAK,IAAI,YAAY,OAAO;AAC3C,SAAO;AAAA,IACL,CAAC,QAAQ,QAAQ,IAAI;AAAA,IACrB,GAAG;AAAA,IACH,GAAI,SAAS,IAAI,gBAAgB,IAAK,CAAC,CAAC,kBAAkB,SAAS,IAAI,gBAAgB,CAAE,CAAC,IAAiB,CAAC;AAAA,IAC5G,CAAC,cAAc,KAAK,SAAS;AAAA,IAC7B,GAAI,QAAQ,WAAW,SAAa,CAAC,CAAC,UAAU,QAAQ,MAAM,CAAC,IAAiB,CAAC;AAAA,IACjF,GAAG,KAAK,OAAO;AAAA,IACf,CAAC,UAAU,UAAU,QAAQ,MAAM,KAAK,cAAc,CAAC;AAAA,IACvD,GAAI,SAAS,IAAI,gBAAgB,IAAK,CAAC,CAAC,kBAAkB,SAAS,IAAI,gBAAgB,CAAE,CAAC,IAAiB,CAAC;AAAA,IAC5G,CAAC,mBAAmB,QAAQ,cAAc;AAAA,IAC1C,GAAI,SAAS,IAAI,gBAAgB,IAAK,CAAC,CAAC,kBAAkB,SAAS,IAAI,gBAAgB,CAAE,CAAC,IAAiB,CAAC;AAAA,IAC5G,CAAC,mBAAmB,KAAK,cAAc;AAAA,IACvC,GAAI,SAAS,IAAI,gBAAgB,IAAK,CAAC,CAAC,kBAAkB,SAAS,IAAI,gBAAgB,CAAE,CAAC,IAAiB,CAAC;AAAA,IAC5G,GAAI,QAAQ,YAAY,SAAa,CAAC,CAAC,WAAW,QAAQ,OAAO,CAAC,IAAiB,CAAC;AAAA,IACpF,GAAG;AAAA,IACH,CAAC,cAAc,YAAY;AAAA,IAC3B,GAAI,QAAQ,WAAW,SAAa,CAAC,CAAC,UAAU,QAAQ,MAAM,CAAC,IAAiB,CAAC;AAAA,EACnF;AACF;AAGA,SAAS,kBAAkB,SAA0B,MAA+B;AAClF,QAAM,EAAE,OAAO,KAAK,IAAI,YAAY,OAAO;AAC3C,SAAO;AAAA,IACL,CAAC,QAAQ,QAAQ,IAAI;AAAA,IACrB,GAAG;AAAA,IACH,CAAC,cAAc,KAAK,SAAS;AAAA,IAC7B,GAAG,KAAK,OAAO;AAAA,IACf,CAAC,UAAU,UAAU,QAAQ,MAAM,KAAK,cAAc,CAAC;AAAA,IACvD,CAAC,mBAAmB,QAAQ,cAAc;AAAA,IAC1C,CAAC,mBAAmB,KAAK,cAAc;AAAA,IACvC,GAAI,QAAQ,YAAY,SAAa,CAAC,CAAC,WAAW,QAAQ,OAAO,CAAC,IAAiB,CAAC;AAAA,IACpF,GAAG;AAAA,IACH,CAAC,cAAc,YAAY;AAAA,IAC3B,GAAI,QAAQ,WAAW,SAAa,CAAC,CAAC,UAAU,QAAQ,MAAM,CAAC,IAAiB,CAAC;AAAA,EACnF;AACF;AAIA,IAAM,gBAAgB;AACtB,IAAM,iBAAiB;AACvB,IAAM,gBAAgB;AACtB,IAAM,gBAAgB;AAEtB,SAAS,SAAS,SAAiB,aAAqB,UAAkB,iBAAyB,QAAiB,OAAiD;AACnK,SAAO;AAAA,IACL,QAAQ,4CAA4C,OAAO,oBAAoB,OAAO;AAAA,IACtF,cAAc,kDAAkD,WAAW,oBAAoB,WAAW;AAAA,IAC1G;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL;AACF;AAIO,IAAM,WAAW;AAAA;AAAA,EAEtB,eAAe;AAAA,IACb,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO,cAAc,SAAS,OAAO,iBAAiB,WAAW,UAAU,KAAK,CAAC;AAAA,EACnF;AAAA,EACA,WAAW;AAAA,IACT,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO,cAAc,SAAS,OAAO,iBAAiB,SAAS,UAAU,OAAO,EAAE,MAAM,MAAM,CAAC,CAAC;AAAA,EAClG;AAAA,EACA,aAAa;AAAA,IACX,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO,cAAc,SAAS,OAAO,iBAAiB,SAAS,UAAU,KAAK,CAAC;AAAA,EACjF;AAAA,EACA,gBAAgB;AAAA,IACd,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO,cAAc,SAAS,OAAO,kBAAkB,aAAa,cAAc,KAAK,CAAC;AAAA,EAC1F;AAAA,EACA,aAAa;AAAA,IACX,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO,cAAc;AAAA,MACnB,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,UAAU;AAAA,MACV,iBAAiB;AAAA,MACjB,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAAA,EACA,SAAS;AAAA,IACP,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO,cAAc;AAAA,MACnB,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,UAAU;AAAA,MACV,iBAAiB;AAAA,MACjB,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAAA,EACA,cAAc;AAAA,IACZ,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO,cAAc;AAAA,MACnB,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,UAAU;AAAA,MACV,iBAAiB;AAAA,MACjB,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAAA,EACA,gBAAgB;AAAA,IACd,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO,cAAc;AAAA,MACnB,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,UAAU;AAAA,MACV,iBAAiB;AAAA,MACjB,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAAA;AAAA,EAEA,cAAc;AAAA,IACZ,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO,cAAc,SAAS,OAAO,aAAa,WAAW,UAAU,KAAK,CAAC;AAAA,EAC/E;AAAA,EACA,eAAe;AAAA,IACb,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO,cAAc;AAAA,MACnB,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,UAAU;AAAA,MACV,iBAAiB;AAAA,MACjB,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,gBAAgB;AAAA,IACd,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA,EACA,YAAY;AAAA,IACV,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA,EACA,cAAc;AAAA,IACZ,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA,EACA,YAAY;AAAA,IACV,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA,EACA,gBAAgB;AAAA,IACd,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA;AAAA,EAGA,WAAW;AAAA,IACT,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA,EACA,WAAW;AAAA,IACT,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA,EACA,YAAY;AAAA,IACV,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA;AAAA,EAEA,WAAW;AAAA,IACT,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA,EACA,YAAY;AAAA,IACV,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA,EACA,SAAS;AAAA,IACP,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA,EACA,eAAe;AAAA,IACb,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA;AAAA,EAGA,eAAe;AAAA,IACb,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO,cAAc,SAAS,OAAO,kBAAkB,WAAW,UAAU,MAAM,EAAE,OAAO,UAAU,CAAC,CAAC;AAAA,EACzG;AAAA,EACA,qBAAqB;AAAA,IACnB,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO,cAAc,SAAS,OAAO,kBAAkB,WAAW,UAAU,OAAO,EAAE,OAAO,UAAU,CAAC,CAAC;AAAA,EAC1G;AAAA,EACA,iBAAiB;AAAA,IACf,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO,cAAc;AAAA,MACnB,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,UAAU;AAAA,MACV,iBAAiB;AAAA,MACjB,QAAQ;AAAA,MACR,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EACA,cAAc;AAAA,IACZ,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO,cAAc;AAAA,MACnB,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,UAAU;AAAA,MACV,iBAAiB;AAAA,MACjB,QAAQ;AAAA,MACR,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EACA,aAAa;AAAA,IACX,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO,cAAc;AAAA,MACnB,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,UAAU;AAAA,MACV,iBAAiB;AAAA,MACjB,QAAQ;AAAA,MACR,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA;AAAA,EAEA,WAAW;AAAA,IACT,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA,EACA,WAAW;AAAA,IACT,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA,EACA,eAAe;AAAA,IACb,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA,EACA,WAAW;AAAA,IACT,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA;AAAA,EAGA,YAAY;AAAA,IACV,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA,EACA,SAAS;AAAA,IACP,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA,EACA,SAAS;AAAA,IACP,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA,EACA,aAAa;AAAA,IACX,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AAAA,EACA,gBAAgB;AAAA,IACd,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,OAAO;AAAA,EACT;AACF;AAGO,IAAM,gBAAgB,OAAO,KAAK,QAAQ;AAG1C,SAAS,QAAQ,MAAmB,UAA0B,CAAC,GAAgB;AACpF,QAAM,UAAU,SAAS,IAAI;AAC7B,QAAM,WAA4B;AAAA,IAChC,GAAG;AAAA,IACH,MAAM,QAAQ,QAAQ;AAAA,IACtB,MAAM,QAAQ,QAAQ;AAAA,IACtB,gBAAgB,QAAQ,kBAAkB,QAAQ;AAAA,EACpD;AACA,SAAO;AAAA,IACL,SAAS,QAAQ,MAAM,UAAU,OAAO;AAAA,IACxC,UAAU;AAAA,IACV,aAAa;AAAA,IACb,GAAI,QAAQ,SAAS,cAAc,EAAE,QAAQ,OAAO,IAAI,CAAC;AAAA,EAC3D;AACF;AAGO,SAAS,YAAY,MAA2B;AACrD,SAAQ,SAAS,IAAI,EAAoB;AAC3C;AAQO,SAAS,MAAM,WAAmB,QAA2B,CAAC,GAAG,OAAO,MAAmB;AAKhG,QAAM,aAAa,IAAI,IAAI,MAAM,IAAI,CAAC,CAAC,IAAI,MAAM,KAAK,YAAY,CAAC,CAAC;AACpE,QAAM,WAAsB,CAAC,CAAC,QAAQ,IAAI,GAAG,CAAC,cAAc,SAAS,GAAG,CAAC,UAAU,KAAK,CAAC,EAAe,OAAO,CAAC,CAAC,IAAI,MAAM,CAAC,WAAW,IAAI,KAAK,YAAY,CAAC,CAAC;AAC9J,SAAO;AAAA,IACL,SAAS,CAAC,GAAG,UAAU,GAAG,KAAK;AAAA,IAC/B,UAAU;AAAA,IACV,aAAa;AAAA,EACf;AACF;AAMO,SAAS,QAAQ,WAAmB,UAA2G,CAAC,GAAgB;AACrK,SAAO;AAAA,IACL,SAAS;AAAA,MACP,CAAC,QAAQ,QAAQ,QAAQ,IAAI;AAAA,MAC7B,CAAC,cAAc,SAAS;AAAA,MACxB,CAAC,UAAU,QAAQ,UAAU,iEAAiE;AAAA,MAC9F,CAAC,mBAAmB,QAAQ,YAAY,mBAAmB;AAAA,MAC3D,GAAI,QAAQ,SAAS,SAAa,CAAC,CAAC,QAAQ,QAAQ,IAAI,CAAC,IAAiB,CAAC;AAAA,MAC3E,CAAC,cAAc,YAAY;AAAA,MAC3B,GAAI,QAAQ,SAAS,CAAC;AAAA,IACxB;AAAA,IACA,UAAU;AAAA,IACV,aAAa;AAAA,EACf;AACF;;;AC/iBO,IAAM,kBAAyC,CAAC,SAAS,QAAQ,UAAU;AAS3E,SAAS,MAAM,OAA0E;AAC9F,SAAO;AAAA,IACL,GAAG;AAAA,IACH,UAAU;AAAA,IACV,QAAQ;AAAA,MACN,aAAa;AAAA,MACb,GAAG,MAAM;AAAA,IACX;AAAA,EACF;AACF;AAGO,SAAS,IAAI,OAAiC;AACnD,SAAO;AACT;AAGO,SAAS,OAAO,UAAuB,OAAe,SAAiB,QAAmD;AAC/H,SAAO,MAAM,KAAK,EAAE,QAAQ,MAAM,GAAG,CAAC,GAAG,WAAW;AAAA,IAClD,GAAG;AAAA,IACH,GAAI,SAAS,EAAE,MAAM,OAAO,KAAK,EAAE,IAAI,CAAC;AAAA,IACxC,MAAM,QAAQ;AAAA,EAChB,EAAE;AACJ;AAQA,IAAM,gBAAgB,CAAC,MAAO,MAAO,MAAO,MAAQ,MAAO,MAAQ,MAAO,MAAO,OAAQ,MAAO,MAAQ,MAAO,MAAO,IAAK;AAEpH,SAAS,WAAW,UAAuB,OAAyC;AACzF,MAAI,KAAK;AACT,SAAO,MAAM,IAAI,CAAC,MAAM,UAAU;AAChC,QAAI,QAAQ,EAAG,OAAM,eAAe,QAAQ,KAAK,cAAc,MAAM;AACrE,WAAO,EAAE,GAAG,UAAU,MAAM,MAAM,GAAG;AAAA,EACvC,CAAC;AACH;;;AChNO,IAAM,wBAAqE;AAAA;AAAA;AAAA;AAAA,EAIhF,QAAQ,CAAC,iBAAiB;AAAA,EAC1B,iBAAiB,CAAC,iBAAiB;AAAA,EACnC,gBAAgB,CAAC,iBAAiB;AAAA,EAClC,WAAW,CAAC,mBAAmB;AAAA,EAC/B,eAAe,CAAC,mBAAmB;AAAA,EACnC,WAAW,CAAC,mBAAmB;AAAA,EAC/B,qBAAqB,CAAC,mBAAmB;AAAA,EACzC,aAAa,CAAC,mBAAmB;AAAA,EACjC,aAAa,CAAC,mBAAmB;AAAA,EACjC,SAAS,CAAC,mBAAmB;AAC/B;AAGO,IAAM,WAA6C;AAAA,EACxD,QAAQ;AAAA,EACR,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,WAAW;AAAA,EACX,eAAe;AAAA,EACf,WAAW;AAAA,EACX,qBAAqB;AAAA,EACrB,aAAa;AAAA,EACb,aAAa;AAAA,EACb,SAAS;AACX;AAGO,IAAM,eAAe;AAQrB,IAAM,eAAe;AACrB,IAAM,gBAAgB;AACtB,IAAM,aAAa;AACnB,IAAM,cAAc;;;AC/B3B,SAAS,GAAG,IAAY,OAAe,WAAmB,UAAkB,YAAoB,OAA6B;AAC3H,SAAO,IAAI;AAAA,IACT;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;AAAA,IACvC,UAAU,CAAC,MAAM,SAAS,CAAC;AAAA,IAC3B,QAAQ,EAAE,SAAS,iBAAiB,SAAS,MAAM,UAAU,WAAW,CAAC,iBAAiB,EAAE;AAAA,EAC9F,CAAC;AACH;AAEO,IAAM,mBAAkC;AAAA;AAAA,EAE7C,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,UAAU,CAAC,gBAAgB;AAAA,IAC3B,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,CAAC,EAAE,GAAG,MAAM,wGAAwG,GAAG,IAAI,SAAS,QAAQ,EAAG,CAAC;AAAA,IAC1J,QAAQ,EAAE,SAAS,gBAAgB,SAAS,MAAM,UAAU,UAAU,WAAW,CAAC,sBAAsB,EAAE;AAAA,IAC1G,MAAM,CAAC,cAAc;AAAA,EACvB,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,CAAC,EAAE,GAAG,MAAM,8KAA8K,GAAG,IAAI,eAAe,CAAC;AAAA,IAC3N,QAAQ,EAAE,SAAS,iBAAiB,UAAU,gBAAgB,SAAS,MAAM,UAAU,aAAa,cAAc,CAAC,sBAAsB,EAAE;AAAA,IAC3I,MAAM,CAAC,cAAc;AAAA,EACvB,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,UAAU,CAAC,gBAAgB;AAAA,IAC3B,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,CAAC,EAAE,GAAG,MAAM,wGAAwG,GAAG,IAAI,aAAa,CAAC;AAAA,IACnJ,QAAQ,EAAE,SAAS,iBAAiB,UAAU,gBAAgB,SAAS,MAAM,UAAU,SAAS;AAAA,IAChG,MAAM,CAAC,gBAAgB,eAAe;AAAA,EACxC,CAAC;AAAA,EACD,GAAG,iBAAiB,iBAAiB,oHAAoH,iBAAiB,wFAAmF,sGAAsG;AAAA,EACnW,GAAG,gBAAgB,gBAAgB,uEAAuE,gBAAgB,8DAA8D,sHAAsH;AAAA;AAAA,EAG9S,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,UAAU,CAAC,gBAAgB;AAAA,IAC3B,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,MAAM,0GAA0G,GAAG,IAAI,SAAS,WAAW,EAAG,CAAC;AAAA,IAC/J,QAAQ,EAAE,SAAS,gBAAgB,SAAS,MAAM,UAAU,YAAY;AAAA,IACxE,MAAM,CAAC,cAAc;AAAA,EACvB,CAAC;AAAA,EACD,GAAG,eAAe,eAAe,yEAAyE,aAAa,yDAAyD;AAAA,EAChL,GAAG,oBAAoB,oBAAoB,mFAAmF,aAAa,uCAAuC;AAAA;AAAA,EAGlL,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,UAAU,CAAC,gBAAgB;AAAA,IAC3B,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,MAAM,yHAAyH,GAAG,IAAI,SAAS,eAAe,EAAG,CAAC;AAAA,IAClL,QAAQ,EAAE,SAAS,gBAAgB,SAAS,MAAM,UAAU,gBAAgB;AAAA,IAC5E,MAAM,CAAC,cAAc;AAAA,EACvB,CAAC;AAAA,EACD,GAAG,mBAAmB,mBAAmB,qLAAqL,iBAAiB,qCAAqC;AAAA,EACpR,GAAG,mBAAmB,mBAAmB,iDAAiD,mBAAmB,6HAAwH,0JAA0J;AAAA,EAC/X,GAAG,qBAAqB,qBAAqB,sFAAsF,qBAAqB,iEAAiE;AAAA,EACzN,GAAG,SAAS,wBAAwB,4CAA4C,SAAS,uEAAuE;AAAA,EAChK,GAAG,sBAAsB,sBAAsB,6FAA6F,WAAW,+DAA+D;AAAA,EACtN,GAAG,wBAAwB,wBAAwB,+FAA+F,WAAW,2CAA2C;AAAA,EACxM,GAAG,aAAa,UAAU,uEAAuE,aAAa,0BAA0B;AAAA,EACxI,GAAG,WAAW,WAAW,kEAAkE,WAAW,2FAAsF;AAAA,EAC5L,GAAG,UAAU,UAAU,sEAAsE,UAAU,wCAAwC;AAAA,EAC/I,GAAG,UAAU,UAAU,0DAA0D,UAAU,qCAAqC;AAAA,EAChI,GAAG,YAAY,YAAY,gEAAgE,YAAY,6BAA6B;AAAA,EAEpI,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,CAAC,MAAM,sJAAsJ,CAAC;AAAA,IACxK,QAAQ,EAAE,SAAS,iBAAiB,SAAS,MAAM,UAAU,aAAa;AAAA,EAC5E,CAAC;AAAA,EAED,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YACE;AAAA,IACF,OACE;AAAA,IACF,UAAU;AAAA,MACR;AAAA,QACE;AAAA,QACA,CAAC,CAAC,UAAU,iEAAiE,GAAG,CAAC,mBAAmB,gBAAgB,GAAG,CAAC,mBAAmB,mBAAmB,CAAC;AAAA,MACjK;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,iBAAiB,UAAU,gBAAgB,SAAS,KAAK;AAAA,IAC5E,MAAM,CAAC,UAAU;AAAA,EACnB,CAAC;AACH;;;AC/HA,IAAM,YAAY,YAAY,eAAe;AAEtC,IAAM,oBAAmC;AAAA;AAAA;AAAA;AAAA,EAI9C,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,CAAC,EAAE,GAAG,MAAM,0EAA0E,GAAG,IAAI,aAAa,CAAC;AAAA,IACrH,KAAK,EAAE,SAAS,EAAE,cAAc,CAAC,gCAAgC,EAAE,EAAE;AAAA,IACrE,QAAQ,EAAE,SAAS,iBAAiB,UAAU,gBAAgB,SAAS,MAAM,UAAU,aAAa,WAAW,CAAC,sBAAsB,EAAE;AAAA,IACxI,MAAM,CAAC,iBAAiB,cAAc;AAAA,EACxC,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,CAAC,EAAE,GAAG,MAAM,0EAA0E,GAAG,IAAI,aAAa,CAAC;AAAA,IACrH,KAAK,CAAC;AAAA,IACN,QAAQ,EAAE,SAAS,iBAAiB,UAAU,gBAAgB,SAAS,MAAM,UAAU,YAAY;AAAA,IACnG,MAAM,CAAC,iBAAiB,cAAc;AAAA,EACxC,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,CAAC,EAAE,GAAG,MAAM,0EAA0E,GAAG,IAAI,aAAa,CAAC;AAAA,IACrH,KAAK,EAAE,SAAS,EAAE,cAAc,CAAC,gCAAgC,EAAE,GAAG,SAAS,EAAE,kCAAkC,CAAC,YAAY,EAAE,EAAE;AAAA,IACpI,QAAQ,EAAE,SAAS,iBAAiB,UAAU,gBAAgB,SAAS,KAAK;AAAA,IAC5E,MAAM,CAAC,iBAAiB,cAAc;AAAA,EACxC,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,MAAM,0EAA0E,GAAG,IAAI,aAAa,CAAC;AAAA,IACrH,KAAK,EAAE,SAAS,EAAE,cAAc,CAAC,uBAAuB,EAAE,GAAG,SAAS,EAAE,yBAAyB,CAAC,YAAY,EAAE,EAAE;AAAA,IAClH,QAAQ,EAAE,SAAS,iBAAiB,UAAU,gBAAgB,SAAS,KAAK;AAAA,IAC5E,MAAM,CAAC,iBAAiB,cAAc;AAAA,EACxC,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,MAAM,yEAAyE,GAAG,IAAI,aAAa,CAAC;AAAA,IACpH,KAAK,EAAE,SAAS,EAAE,cAAc,CAAC,+BAA+B,EAAE,EAAE;AAAA,IACpE,QAAQ,EAAE,SAAS,iBAAiB,UAAU,gBAAgB,SAAS,KAAK;AAAA,IAC5E,MAAM,CAAC,eAAe;AAAA,EACxB,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,UAAU,CAAC,gBAAgB;AAAA,IAC3B,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,MAAM,mEAAmE,GAAG,IAAI,aAAa,CAAC;AAAA,IAC9G,QAAQ,EAAE,SAAS,iBAAiB,UAAU,gBAAgB,SAAS,MAAM,UAAU,YAAY;AAAA,IACnG,MAAM,CAAC,iBAAiB,cAAc;AAAA,EACxC,CAAC;AAAA;AAAA;AAAA;AAAA,EAKD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,CAAC,EAAE,SAAS,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,SAAS,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,eAAe,CAAC,GAAG,UAAU,SAAS,aAAa,MAAM,CAAC;AAAA,IAC7K,QAAQ,EAAE,SAAS,iBAAiB,SAAS,OAAO,UAAU,IAAI,WAAW,CAAC,kBAAkB,EAAE;AAAA,IAClG,MAAM,CAAC,SAAS;AAAA,EAClB,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU;AAAA,MACR;AAAA,QACE,SAAS;AAAA,UACP,CAAC,QAAQ,cAAc;AAAA,UACvB,CAAC,mBAAmB,mBAAmB;AAAA,UACvC,CAAC,UAAU,uFAAuF;AAAA,UAClG,CAAC,mBAAmB,gBAAgB;AAAA,UACpC,CAAC,cAAc,SAAS;AAAA,UACxB,CAAC,kBAAkB,MAAM;AAAA,UACzB,CAAC,kBAAkB,UAAU;AAAA,UAC7B,CAAC,kBAAkB,IAAI;AAAA,UACvB,CAAC,kBAAkB,UAAU;AAAA,UAC7B,CAAC,6BAA6B,GAAG;AAAA,UACjC,CAAC,cAAc,YAAY;AAAA,QAC7B;AAAA,QACA,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO,WAAW,CAAC,cAAc,GAAG,aAAa,CAAC,MAAM,EAAE;AAAA,IAC7E,MAAM,CAAC,SAAS;AAAA,EAClB,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,CAAC,QAAQ,eAAe,CAAC;AAAA,IACnC,QAAQ,EAAE,SAAS,WAAW,UAAU,EAAE;AAAA,IAC1C,MAAM,CAAC,WAAW,aAAa;AAAA,EACjC,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,MAAM,KAAK,EAAE,QAAQ,GAAG,GAAG,CAAC,GAAG,WAAW,EAAE,GAAG,QAAQ,eAAe,GAAG,MAAM,aAAa,QAAQ,CAAC,IAAI,MAAM,QAAQ,IAAM,EAAE;AAAA,IACzI,QAAQ,EAAE,SAAS,OAAO,WAAW,CAAC,SAAS,GAAG,aAAa,CAAC,MAAM,EAAE;AAAA,IACxE,MAAM,CAAC,SAAS;AAAA,EAClB,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU;AAAA,MACR,EAAE,GAAG,QAAQ,eAAe,GAAG,MAAM,iBAAiB,MAAM,EAAE;AAAA,MAC9D,EAAE,GAAG,QAAQ,eAAe,GAAG,MAAM,iBAAiB,MAAM,KAAM;AAAA,MAClE,EAAE,GAAG,QAAQ,eAAe,GAAG,MAAM,iBAAiB,MAAM,MAAO;AAAA,IACrE;AAAA,IACA,QAAQ,EAAE,SAAS,WAAW,UAAU,GAAG;AAAA,IAC3C,MAAM,CAAC,WAAW,aAAa;AAAA,EACjC,CAAC;AAAA;AAAA;AAAA;AAAA,EAKD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,CAAC,EAAE,GAAG,QAAQ,eAAe,GAAG,MAAM,uBAAuB,CAAC;AAAA,IACxE,QAAQ,EAAE,SAAS,iBAAiB,SAAS,MAAM,WAAW,CAAC,MAAM,EAAE;AAAA,EACzE,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,CAAC,6BAA6B;AAAA,IACxC,UAAU,CAAC,EAAE,GAAG,QAAQ,eAAe,GAAG,QAAQ,QAAQ,MAAM,iDAAiD,CAAC;AAAA,IAClH,QAAQ,EAAE,SAAS,iBAAiB,SAAS,MAAM,WAAW,CAAC,MAAM,EAAE;AAAA,EACzE,CAAC;AAAA;AAAA;AAAA;AAAA,EAKD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,MAAM,KAAK,EAAE,QAAQ,GAAG,GAAG,CAAC,GAAG,WAAW,EAAE,GAAG,QAAQ,eAAe,GAAG,QAAQ,QAAQ,MAAM,UAAU,MAAM,QAAQ,IAAI,EAAE;AAAA,IACvI,QAAQ,EAAE,SAAS,OAAO,aAAa,CAAC,MAAM,EAAE;AAAA,IAChD,MAAM,CAAC,QAAQ;AAAA,EACjB,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,MAAM,KAAK,EAAE,QAAQ,GAAG,GAAG,CAAC,GAAG,WAAW;AAAA,MAClD,SAAS,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,wBAAwB,GAAG,CAAC,mBAAmB,eAAe,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,gBAAgB,mCAAmC,CAAC;AAAA,MAC5L,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,MACV,aAAa;AAAA,MACb,MAAM,QAAQ;AAAA,IAChB,EAAE;AAAA,IACF,QAAQ,EAAE,SAAS,iBAAiB,UAAU,eAAe,SAAS,KAAK;AAAA,IAC3E,MAAM,CAAC,QAAQ;AAAA,EACjB,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,UAAU,aAAa,SAAS,gBAAgB,eAAe,eAAe,gBAAgB,iBAAiB,iBAAiB,gBAAgB,EAAE,IAAI,CAAC,MAAM,WAAW;AAAA,MACjL,GAAG,MAAM,uGAAuG;AAAA,MAChH;AAAA,MACA,MAAM,QAAQ;AAAA,IAChB,EAAE;AAAA,IACF,QAAQ,EAAE,SAAS,OAAO,aAAa,CAAC,MAAM,EAAE;AAAA,IAChD,OAAO;AAAA,IACP,MAAM,CAAC,UAAU,aAAa;AAAA,EAChC,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,CAAC,EAAE,SAAS,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,SAAS,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,cAAc,YAAY,GAAG,CAAC,qBAAqB,SAAS,CAAC,GAAG,UAAU,SAAS,aAAa,MAAM,CAAC;AAAA,IACvM,QAAQ,EAAE,SAAS,iBAAiB,SAAS,MAAM,WAAW,CAAC,kBAAkB,EAAE;AAAA,EACrF,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU;AAAA,MACR,EAAE,GAAG,MAAM,YAAY,eAAe,CAAC,GAAG,MAAM,QAAQ,MAAM,EAAE;AAAA,MAChE,EAAE,GAAG,MAAM,YAAY,gBAAgB,CAAC,GAAG,MAAM,QAAQ,MAAM,IAAI;AAAA,MACnE,EAAE,GAAG,MAAM,YAAY,WAAW,CAAC,GAAG,MAAM,QAAQ,MAAM,IAAI;AAAA,MAC9D,EAAE,GAAG,MAAM,YAAY,aAAa,CAAC,GAAG,MAAM,QAAQ,MAAM,KAAM;AAAA,MAClE,EAAE,GAAG,MAAM,YAAY,eAAe,CAAC,GAAG,MAAM,QAAQ,MAAM,KAAM;AAAA,IACtE;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO,aAAa,CAAC,MAAM,EAAE;AAAA,IAChD,MAAM,CAAC,WAAW,aAAa;AAAA,EACjC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,CAAC,EAAE,GAAG,MAAM,uIAAuI,CAAC,CAAC,mBAAmB,gBAAgB,GAAG,CAAC,mBAAmB,mBAAmB,CAAC,CAAC,EAAE,CAAC;AAAA,IACjP,QAAQ,EAAE,SAAS,OAAO,WAAW,CAAC,cAAc,GAAG,aAAa,CAAC,SAAS,MAAM,EAAE;AAAA,IACtF,MAAM,CAAC,WAAW,YAAY;AAAA,EAChC,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,MAAM,sJAAsJ,CAAC,CAAC,mBAAmB,gBAAgB,GAAG,CAAC,mBAAmB,mBAAmB,CAAC,CAAC,EAAE,CAAC;AAAA,IAChQ,QAAQ,EAAE,SAAS,OAAO,WAAW,CAAC,cAAc,GAAG,aAAa,CAAC,SAAS,MAAM,EAAE;AAAA,IACtF,MAAM,CAAC,WAAW,YAAY;AAAA,EAChC,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,CAAC,EAAE,GAAG,MAAM,kGAAkG,CAAC,CAAC,mBAAmB,gBAAgB,GAAG,CAAC,mBAAmB,mBAAmB,CAAC,CAAC,EAAE,CAAC;AAAA,IAC5M,QAAQ,EAAE,SAAS,OAAO,WAAW,CAAC,cAAc,GAAG,aAAa,CAAC,SAAS,MAAM,EAAE;AAAA,IACtF,MAAM,CAAC,WAAW,YAAY;AAAA,EAChC,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,CAAC,EAAE,GAAG,QAAQ,eAAe,GAAG,SAAS,QAAQ,eAAe,EAAE,QAAQ,IAAI,CAAC,WAAY,OAAO,CAAC,MAAM,eAAgB,CAAC,cAAc,gHAAgH,IAAc,MAAO,EAAE,CAAC;AAAA,IAC1R,QAAQ,EAAE,SAAS,OAAO,WAAW,CAAC,cAAc,GAAG,UAAU,IAAI,aAAa,CAAC,SAAS,MAAM,EAAE;AAAA,IACpG,MAAM,CAAC,WAAW,cAAc,YAAY;AAAA,EAC9C,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,CAAC,EAAE,GAAG,QAAQ,eAAe,GAAG,MAAM,QAAQ,CAAC;AAAA,IACzD,QAAQ,EAAE,SAAS,OAAO,WAAW,CAAC,iBAAiB,GAAG,aAAa,CAAC,SAAS,MAAM,EAAE;AAAA,IACzF,MAAM,CAAC,UAAU;AAAA,EACnB,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR,EAAE,GAAG,QAAQ,eAAe,GAAG,MAAM,gBAAgB,MAAM,EAAE;AAAA,MAC7D,EAAE,GAAG,QAAQ,eAAe,GAAG,MAAM,cAAc,MAAM,IAAI;AAAA,IAC/D;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO,WAAW,CAAC,iBAAiB,GAAG,aAAa,CAAC,SAAS,MAAM,EAAE;AAAA,IACzF,MAAM,CAAC,UAAU;AAAA,EACnB,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,QAAQ,eAAe,GAAG,MAAM,6DAA6D,CAAC;AAAA,IAC9G,QAAQ,EAAE,SAAS,OAAO,WAAW,CAAC,iBAAiB,GAAG,aAAa,CAAC,SAAS,MAAM,EAAE;AAAA,IACzF,MAAM,CAAC,UAAU;AAAA,EACnB,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,MAAM,SAAS,GAAG,QAAQ,QAAQ,CAAC;AAAA,IACnD,QAAQ,EAAE,SAAS,OAAO,WAAW,CAAC,iBAAiB,GAAG,aAAa,CAAC,SAAS,MAAM,EAAE;AAAA,IACzF,MAAM,CAAC,UAAU;AAAA,EACnB,CAAC;AAAA;AAAA;AAAA;AAAA,EAKD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU;AAAA,MACR;AAAA,QACE,SAAS,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,SAAS,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,gBAAgB,mCAAmC,GAAG,CAAC,kBAAkB,GAAG,GAAG,CAAC,qBAAqB,SAAS,CAAC;AAAA,QAClM,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,iBAAiB,SAAS,MAAM,WAAW,CAAC,kBAAkB,EAAE;AAAA,IACnF,MAAM,CAAC,UAAU;AAAA,EACnB,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE,SAAS,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,QAAQ,uBAAuB,GAAG,CAAC,cAAc,SAAS,GAAG,CAAC,UAAU,KAAK,CAAC;AAAA,QACnH,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,iBAAiB,SAAS,MAAM,WAAW,CAAC,kBAAkB,EAAE;AAAA,IACnF,MAAM,CAAC,UAAU;AAAA,EACnB,CAAC;AACH;;;ACzZA,SAAS,SAAS,IAAY,OAAe,UAAkB,WAAmB,YAAoB,UAAkB,OAA2C;AACjK,SAAO,IAAI;AAAA,IACT;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,UAAU,CAAC,MAAM,SAAS,CAAC;AAAA,IAC3B,QAAQ,EAAE,SAAS,iBAAiB,SAAS,MAAM,UAAU,WAAW,CAAC,iBAAiB,EAAE;AAAA,IAC5F,GAAG;AAAA,EACL,CAAC;AACH;AAEA,IAAM,kBAA0C;AAAA,EAC9C,CAAC,QAAQ,cAAc;AAAA,EACvB,CAAC,UAAU,iEAAiE;AAAA,EAC5E,CAAC,mBAAmB,mBAAmB;AAAA,EACvC,CAAC,QAAQ,4BAA4B;AACvC;AAEA,IAAM,WAAwB,EAAE,SAAS,gBAAgB,SAAS,MAAM,WAAW,CAAC,sBAAsB,EAAE;AAErG,IAAM,mBAAkC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM7C,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,SAAS,CAAC,CAAC,cAAc,0EAA0E,GAAG,GAAG,eAAe,GAAG,IAAI,cAAc,UAAU,SAAS,aAAa,MAAM,CAAC;AAAA,IACjM,KAAK,EAAE,SAAS,EAAE,CAAC,YAAY,GAAG,CAAC,aAAa,EAAE,GAAG,SAAS,EAAE,CAAC,aAAa,GAAG,CAAC,YAAY,EAAE,EAAE;AAAA,IAClG,QAAQ,EAAE,GAAG,UAAU,UAAU,YAAY;AAAA,IAC7C,MAAM,CAAC,cAAc;AAAA,EACvB,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE,SAAS,CAAC,CAAC,cAAc,mMAAmM,GAAG,GAAG,eAAe;AAAA,QACjP,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,KAAK,EAAE,SAAS,EAAE,CAAC,YAAY,GAAG,CAAC,aAAa,EAAE,GAAG,SAAS,EAAE,CAAC,aAAa,GAAG,CAAC,YAAY,EAAE,EAAE;AAAA,IAClG,QAAQ,EAAE,GAAG,UAAU,UAAU,YAAY;AAAA,IAC7C,OAAO;AAAA,IACP,MAAM,CAAC,cAAc;AAAA,EACvB,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,SAAS,CAAC,CAAC,cAAc,yEAAyE,GAAG,GAAG,eAAe,GAAG,IAAI,YAAY,UAAU,SAAS,aAAa,MAAM,CAAC;AAAA,IAC9L,KAAK,EAAE,SAAS,EAAE,CAAC,UAAU,GAAG,CAAC,WAAW,EAAE,GAAG,SAAS,EAAE,CAAC,WAAW,GAAG,CAAC,UAAU,EAAE,EAAE;AAAA,IAC1F,QAAQ,EAAE,GAAG,UAAU,UAAU,UAAU;AAAA,IAC3C,MAAM,CAAC,cAAc;AAAA,EACvB,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA;AAAA;AAAA;AAAA,IAIF,UAAU,CAAC,EAAE,SAAS,CAAC,CAAC,cAAc,0EAA0E,GAAG,GAAG,eAAe,GAAG,IAAI,gBAAgB,UAAU,SAAS,aAAa,MAAM,CAAC;AAAA,IACnM,KAAK,EAAE,aAAa,KAAK;AAAA,IACzB,QAAQ,EAAE,SAAS,iBAAiB,UAAU,gBAAgB,SAAS,MAAM,UAAU,aAAa,cAAc,CAAC,sBAAsB,EAAE;AAAA,IAC3I,MAAM,CAAC,gBAAgB,YAAY;AAAA,EACrC,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,UAAU,CAAC,gBAAgB;AAAA,IAC3B,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,MAAM,qFAAqF,GAAG,IAAI,SAAS,aAAa,EAAG,CAAC;AAAA,IAC5I,QAAQ,EAAE,GAAG,UAAU,UAAU,cAAc;AAAA,IAC/C,MAAM,CAAC,cAAc;AAAA,EACvB,CAAC;AAAA;AAAA;AAAA;AAAA,EAKD,SAAS,aAAa,aAAa,kBAAkB,oEAAoE,mDAAmD,WAAW;AAAA,EACvL,SAAS,eAAe,eAAe,kBAAkB,uFAAuF,0CAA0C,aAAa;AAAA,EACvM,SAAS,YAAY,YAAY,kBAAkB,2KAA2K,yCAAyC,UAAU;AAAA,EACjR,SAAS,aAAa,aAAa,kBAAkB,wFAAwF,yCAAyC,WAAW;AAAA,EACjM,SAAS,cAAc,cAAc,kBAAkB,4DAA4D,6CAA6C,YAAY;AAAA,EAC5K,SAAS,YAAY,YAAY,kBAAkB,yKAAyK,2BAA2B,UAAU;AAAA,EACjQ,SAAS,YAAY,YAAY,kBAAkB,wEAAwE,4CAA4C,UAAU;AAAA,EACjL,SAAS,aAAa,aAAa,kBAAkB,8EAA8E,iFAA4E,QAAQ;AAAA,EACvN,SAAS,cAAc,qBAAqB,kBAAkB,yEAAyE,6BAA6B,YAAY;AAAA,EAChL,SAAS,SAAS,gBAAgB,kBAAkB,2EAA2E,iCAAiC,OAAO;AAAA;AAAA;AAAA,EAIvK,SAAS,yBAAyB,yBAAyB,kBAAkB,wDAAwD,sFAAsF,WAAW;AAAA,EACtO,SAAS,mBAAmB,mBAAmB,kBAAkB,4KAA4K,yEAAyE,WAAW;AAAA,EACjU,SAAS,eAAe,eAAe,kBAAkB,yCAAyC,2DAA2D,WAAW;AAAA,EACxK,SAAS,iBAAiB,iBAAiB,eAAe,sDAAsD,uEAAuE,eAAe;AAAA,EACtM,SAAS,wBAAwB,wBAAwB,eAAe,wBAAwB,8DAA8D,eAAe;AAAA,EAC7K,SAAS,sBAAsB,sBAAsB,eAAe,iEAAiE,qCAAqC,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA,EAMvL,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,UAAU,CAAC,gBAAgB;AAAA,IAC3B,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,MAAM,2EAA2E,GAAG,IAAI,SAAS,mBAAmB,EAAG,CAAC;AAAA,IACxI,QAAQ,EAAE,GAAG,UAAU,UAAU,oBAAoB;AAAA,IACrD,MAAM,CAAC,cAAc;AAAA,EACvB,CAAC;AAAA,EACD,SAAS,cAAc,cAAc,iBAAiB,kBAAkB,mCAAmC,YAAY;AAAA,EACvH,SAAS,eAAe,eAAe,iBAAiB,yFAAyF,wIAAmI,aAAa;AAAA,EACjS,SAAS,0BAA0B,0BAA0B,iBAAiB,8DAA8D,4CAA4C,UAAU;AAAA,EAClM,SAAS,qBAAqB,qBAAqB,iBAAiB,kDAAkD,qCAAqC,UAAU;AAAA,EACrK,SAAS,cAAc,cAAc,iBAAiB,qEAAqE,0BAA0B,YAAY;AAAA,EACjK,SAAS,eAAe,eAAe,iBAAiB,iCAAiC,kCAAkC,aAAa;AAAA,EACxI,SAAS,oBAAoB,yBAAyB,iBAAiB,yBAAyB,2DAA2D,UAAU;AAAA,EACrK,SAAS,aAAa,aAAa,iBAAiB,4EAA4E,gCAAgC,WAAW;AAAA,EAC3K,SAAS,gBAAgB,gBAAgB,iBAAiB,kFAAkF,qCAAqC,cAAc;AAAA,EAC/L,SAAS,oBAAoB,yBAAyB,iBAAiB,6DAA6D,+GAA+G,UAAU;AAAA,EAC7P,SAAS,WAAW,WAAW,iBAAiB,oEAAoE,wEAAwE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,EAMrM,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,UAAU,CAAC,gBAAgB;AAAA,IAC3B,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,MAAM,wEAAwE,GAAG,IAAI,SAAS,aAAa,EAAG,CAAC;AAAA,IAC/H,QAAQ,EAAE,GAAG,UAAU,UAAU,cAAc;AAAA,IAC/C,MAAM,CAAC,cAAc;AAAA,EACvB,CAAC;AAAA,EACD,SAAS,WAAW,WAAW,cAAc,mFAAmF,2BAA2B,SAAS;AAAA,EACpK,SAAS,cAAc,cAAc,cAAc,wCAAwC,mDAAmD,YAAY;AAAA,EAC1J,SAAS,iBAAiB,gBAAgB,cAAc,yFAAyF,mDAAmD,cAAc;AAAA,EAClN,SAAS,YAAY,YAAY,cAAc,qEAAqE,+CAA+C,UAAU;AAAA,EAC7K,SAAS,cAAc,qBAAqB,cAAc,2IAA2I,kDAAkD,kBAAkB;AAAA,EACzQ,SAAS,YAAY,YAAY,cAAc,kHAAkH,kCAAkC,UAAU;AAAA,EAC7M,SAAS,sBAAsB,sBAAsB,cAAc,4HAA4H,oCAAoC,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,EAM5O,SAAS,UAAU,UAAU,eAAe,8FAA8F,2FAA2F,QAAQ;AAAA,EAC7O,SAAS,aAAa,aAAa,eAAe,6EAA6E,+BAA+B,WAAW;AAAA,EACzK,SAAS,YAAY,YAAY,eAAe,iDAAiD,0FAA0F,UAAU;AAAA,EACrM,SAAS,eAAe,eAAe,eAAe,sDAAsD,+BAA+B,aAAa;AAAA,EACxJ,SAAS,YAAY,YAAY,eAAe,mEAAmE,0FAA0F,YAAY;AAAA,EACzN,SAAS,YAAY,YAAY,kBAAkB,qFAAqF,+BAA+B,YAAY,EAAE,UAAU,aAAa,CAAC;AAAA,EAC7M,SAAS,eAAe,gBAAgB,kBAAkB,wEAAwE,+BAA+B,aAAa;AAAA,EAC9K,SAAS,cAAc,cAAc,kBAAkB,qBAAqB,+BAA+B,YAAY;AAAA,EACvH,SAAS,iBAAiB,kBAAkB,kBAAkB,uEAAuE,+BAA+B,eAAe;AAAA,EAEnL,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,CAAC,MAAM,aAAa,CAAC;AAAA,IAC/B,QAAQ,EAAE,SAAS,iBAAiB,UAAU,eAAe,SAAS,OAAO,aAAa,CAAC,MAAM,EAAE;AAAA,EACrG,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,CAAC,MAAM,MAAM,CAAC;AAAA,IACxB,QAAQ,EAAE,SAAS,iBAAiB,SAAS,MAAM,UAAU,kBAAkB,aAAa,CAAC,MAAM,EAAE;AAAA,EACvG,CAAC;AAAA;AAAA;AAAA;AAAA,EAKD,SAAS,oBAAoB,oBAAoB,WAAW,yFAAyF,mFAAmF,aAAa;AAAA,EACrP,SAAS,YAAY,YAAY,WAAW,qEAAqE,sDAAsD,UAAU;AACnL;;;ACtNA,SAAS,gBAAgB,IAAY,OAAe,WAAmB,YAAoB,UAAoG,CAAC,GAAgB;AAC9M,SAAO,IAAI;AAAA,IACT;AAAA,IACA;AAAA,IACA,UAAU,QAAQ,YAAY;AAAA,IAC9B,UAAU;AAAA,IACV;AAAA,IACA,GAAI,QAAQ,UAAU,SAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;AAAA,IAC9D,UAAU,CAAC,QAAQ,WAAW,QAAQ,SAAS,SAAY,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC;AAAA,IACvF,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,MACT,WAAW,CAAC,iBAAiB;AAAA,MAC7B,GAAI,QAAQ,aAAa,SAAY,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,IACzE;AAAA,EACF,CAAC;AACH;AAEO,IAAM,yBAAwC;AAAA;AAAA,EAEnD,gBAAgB,iBAAiB,cAAc,6DAA6D,mFAAmF,EAAE,UAAU,aAAa,CAAC;AAAA,EACzN,gBAAgB,wBAAwB,4BAA4B,+JAA+J,0CAA0C,EAAE,UAAU,aAAa,CAAC;AAAA,EACvS,gBAAgB,WAAW,UAAU,+EAA+E,iDAAiD;AAAA,EACrK,gBAAgB,mBAAmB,6BAA6B,uMAAuM,0EAA0E,EAAE,UAAU,cAAc,CAAC;AAAA,EAC5W,gBAAgB,kBAAkB,qBAAqB,+DAA+D,6EAA6E,EAAE,UAAU,cAAc,CAAC;AAAA,EAC9N,gBAAgB,iBAAiB,qBAAqB,4EAA4E,0EAA0E,EAAE,UAAU,QAAQ,CAAC;AAAA,EACjO,gBAAgB,iBAAiB,aAAa,kFAAkF,2DAA2D,CAAC,CAAC;AAAA,EAC7L,gBAAgB,aAAa,eAAe,4LAA4L,0EAA0E,CAAC,CAAC;AAAA,EACpT,gBAAgB,wBAAwB,4BAA4B,4MAA4M,kEAAkE,EAAE,UAAU,cAAc,UAAU,gBAAgB,OAAO,qHAAqH,CAAC;AAAA;AAAA,EAGngB,gBAAgB,oBAAoB,gBAAgB,uEAAuE,6EAA6E,EAAE,UAAU,YAAY,CAAC;AAAA,EACjO,gBAAgB,oBAAoB,mBAAmB,qMAAqM,iCAAiC,EAAE,UAAU,YAAY,CAAC;AAAA,EACtT,gBAAgB,2BAA2B,0BAA0B,iFAAiF,8DAA8D,EAAE,UAAU,YAAY,CAAC;AAAA,EAC7O,gBAAgB,cAAc,eAAe,2FAA2F,+DAA+D,CAAC,CAAC;AAAA,EACzM,gBAAgB,oBAAoB,oBAAoB,+FAA+F,oDAAoD,EAAE,UAAU,YAAY,CAAC;AAAA;AAAA,EAGpO,gBAAgB,aAAa,cAAc,qFAAqF,2DAA2D,CAAC,CAAC;AAAA,EAC7L,gBAAgB,mBAAmB,oBAAoB,uFAAuF,2BAA2B,CAAC,CAAC;AAAA,EAC3K,gBAAgB,mBAAmB,0BAA0B,yKAAyK,mFAAmF,EAAE,UAAU,WAAW,CAAC;AAAA;AAAA,EAGjV,gBAAgB,YAAY,YAAY,wEAAwE,+FAA+F,EAAE,UAAU,WAAW,CAAC;AAAA,EACvO,gBAAgB,aAAa,aAAa,8EAA8E,8FAAyF,EAAE,UAAU,SAAS,CAAC;AAAA,EACvO,gBAAgB,gBAAgB,aAAa,gFAAgF,wCAAwC,CAAC,CAAC;AAAA,EACvK,gBAAgB,aAAa,UAAU,oEAAoE,gEAAgE,CAAC,CAAC;AAAA,EAC7K,gBAAgB,mBAAmB,gBAAgB,qGAAqG,qEAAqE,CAAC,CAAC;AAAA,EAC/N,gBAAgB,WAAW,eAAe,gEAAgE,4FAA4F,CAAC,CAAC;AAAA,EACxM,gBAAgB,aAAa,UAAU,yGAAyG,oCAAoC,CAAC,CAAC;AAAA,EACtL,gBAAgB,gBAAgB,YAAY,wEAAwE,6BAA6B,CAAC,CAAC;AAAA,EACnJ,gBAAgB,gBAAgB,aAAa,4FAA4F,sCAAsC,CAAC,CAAC;AAAA;AAAA,EAGjL,gBAAgB,sBAAsB,mBAAmB,uBAAuB,gFAAgF,EAAE,UAAU,aAAa,OAAO,2MAA2M,CAAC;AAAA,EAC5Y,gBAAgB,qBAAqB,kBAAkB,kBAAkB,4CAA4C,EAAE,UAAU,YAAY,CAAC;AAAA,EAC9I,gBAAgB,sBAAsB,mBAAmB,uBAAuB,0BAA0B,EAAE,UAAU,YAAY,CAAC;AAAA,EACnI,gBAAgB,qBAAqB,kBAAkB,wHAAwH,qDAAqD,EAAE,UAAU,aAAa,OAAO,mOAA8N,CAAC;AAAA,EACne,gBAAgB,wBAAwB,qBAAqB,wNAAwN,kFAA6E,EAAE,UAAU,aAAa,OAAO,8GAA8G,CAAC;AAAA,EACjf,gBAAgB,+BAA+B,4BAA4B,0DAA0D,0FAA0F,EAAE,UAAU,YAAY,CAAC;AAAA,EACxP,gBAAgB,mBAAmB,eAAe,6FAA6F,wFAAwF,EAAE,UAAU,UAAU,CAAC;AAAA,EAC9P,gBAAgB,eAAe,YAAY,4EAA4E,gDAAgD,EAAE,UAAU,UAAU,CAAC;AAAA,EAC9L,gBAAgB,mBAAmB,gBAAgB,wDAAwD,mEAAmE,EAAE,UAAU,UAAU,CAAC;AAAA,EACrM,gBAAgB,iBAAiB,qBAAqB,sLAAsL,gGAAgG,EAAE,UAAU,WAAW,CAAC;AACtW;;;ACjEA,SAAS,SACP,IACA,OACA,UACA,WACA,YACA,UAAsG,CAAC,GAC1F;AACb,SAAO,IAAI;AAAA,IACT;AAAA,IACA;AAAA,IACA,UAAU,QAAQ,YAAY;AAAA,IAC9B;AAAA,IACA;AAAA,IACA,GAAI,QAAQ,UAAU,SAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;AAAA,IAC9D,UAAU,CAAC,QAAQ,WAAW,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC,CAAC,CAAC;AAAA,IAC7F,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,MACT,WAAW,CAAC,iBAAiB;AAAA,MAC7B,GAAI,QAAQ,aAAa,SAAY,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,IACzE;AAAA,EACF,CAAC;AACH;AAEO,IAAM,yBAAwC;AAAA;AAAA,EAEnD,SAAS,sBAAsB,qCAAqC,oBAAoB,sFAAsF,uGAAuG,EAAE,UAAU,aAAa,UAAU,aAAa,CAAC;AAAA,EACtU,SAAS,aAAa,2BAA2B,oBAAoB,2EAA2E,2FAA2F,CAAC,CAAC;AAAA,EAC7O,SAAS,aAAa,UAAU,oBAAoB,wEAAwE,gCAAgC,CAAC,CAAC;AAAA,EAC9J,SAAS,kBAAkB,eAAe,oBAAoB,oFAAoF,kCAAkC,CAAC,CAAC;AAAA,EACtL,SAAS,oBAAoB,8BAA8B,oBAAoB,iFAAiF,wFAAwF,CAAC,CAAC;AAAA,EAC1P,SAAS,mBAAmB,gCAAgC,oBAAoB,+EAA+E,oFAAoF,EAAE,UAAU,gBAAgB,OAAO,gMAA2L,CAAC;AAAA,EACld,SAAS,oBAAoB,2BAA2B,oBAAoB,0FAA0F,6EAA6E,EAAE,UAAU,eAAe,CAAC;AAAA;AAAA,EAG/Q,SAAS,aAAa,wBAAwB,oBAAoB,kFAAkF,oEAAoE,EAAE,UAAU,aAAa,CAAC;AAAA,EAClP,SAAS,aAAa,UAAU,oBAAoB,0EAA0E,iCAAiC,EAAE,UAAU,aAAa,CAAC;AAAA,EACzL,SAAS,cAAc,0BAA0B,oBAAoB,0EAA0E,wCAAwC,CAAC,CAAC;AAAA,EACzL,SAAS,iBAAiB,cAAc,oBAAoB,gFAAgF,uEAAuE,CAAC,CAAC;AAAA,EACrN,SAAS,sBAAsB,uCAAuC,oBAAoB,qFAAqF,+DAA+D,CAAC,CAAC;AAAA;AAAA,EAGhP,SAAS,eAAe,YAAY,eAAe,+IAA+I,sGAAsG,EAAE,UAAU,cAAc,OAAO,2JAA2J,CAAC;AAAA,EACre,SAAS,kBAAkB,+BAA+B,gBAAgB,6EAA6E,iEAAiE,EAAE,UAAU,aAAa,UAAU,aAAa,CAAC;AAAA,EACzQ,SAAS,iBAAiB,cAAc,gBAAgB,iFAAiF,+CAA+C,EAAE,UAAU,YAAY,UAAU,aAAa,CAAC;AAAA,EACxO,SAAS,gBAAgB,aAAa,gBAAgB,8EAA8E,+DAA+D,EAAE,UAAU,aAAa,CAAC;AAAA,EAC7N,SAAS,gBAAgB,aAAa,gBAAgB,oFAAoF,0DAA0D,EAAE,UAAU,aAAa,CAAC;AAAA,EAC9N,SAAS,oBAAoB,iBAAiB,eAAe,gFAAgF,4CAA4C,EAAE,UAAU,aAAa,CAAC;AAAA;AAAA,EAGnN,SAAS,uBAAuB,oBAAoB,oBAAoB,8FAA8F,wCAAwC,EAAE,UAAU,aAAa,CAAC;AAAA,EACxO,SAAS,eAAe,YAAY,oBAAoB,uEAAuE,4EAA4E,EAAE,UAAU,aAAa,CAAC;AAAA,EACrO,SAAS,eAAe,YAAY,oBAAoB,qGAAqG,wCAAwC,EAAE,UAAU,aAAa,CAAC;AAAA,EAC/N,SAAS,YAAY,+BAA+B,oBAAoB,2HAA2H,+FAA+F,EAAE,UAAU,cAAc,OAAO,8HAA8H,CAAC;AAAA,EAClc,SAAS,wBAAwB,6BAA6B,oBAAoB,4CAA4C,2DAA2D,EAAE,UAAU,QAAQ,CAAC;AAAA;AAAA,EAG9M,SAAS,kBAAkB,qCAAqC,sBAAsB,4FAA4F,yEAAyE,EAAE,UAAU,cAAc,OAAO,qJAAqJ,CAAC;AAAA,EAClb,SAAS,kBAAkB,eAAe,sBAAsB,8EAA8E,8DAA8D,EAAE,UAAU,aAAa,CAAC;AAAA,EACtO,SAAS,oBAAoB,qBAAqB,sBAAsB,sDAAsD,0CAA0C,EAAE,UAAU,aAAa,CAAC;AAAA,EAClM,SAAS,eAAe,YAAY,sBAAsB,0FAA0F,wEAAwE,EAAE,UAAU,aAAa,CAAC;AAAA;AAAA,EAGtP,SAAS,uBAAuB,8BAA8B,uBAAuB,wFAAwF,uDAAuD,CAAC,CAAC;AAAA,EACtO,SAAS,qBAAqB,mCAAmC,uBAAuB,+EAA+E,mDAAmD,CAAC,CAAC;AAAA,EAC5N,SAAS,qBAAqB,oCAAoC,uBAAuB,kFAAkF,0DAA0D,CAAC,CAAC;AAAA;AAAA,EAGvO,SAAS,oBAAoB,iBAAiB,mBAAmB,wEAAwE,gEAAgE,EAAE,UAAU,aAAa,CAAC;AAAA,EACnO,SAAS,eAAe,YAAY,mBAAmB,+DAA+D,gFAAgF,EAAE,UAAU,aAAa,CAAC;AAAA,EAChO,SAAS,uBAAuB,kCAAkC,mBAAmB,gGAAgG,0FAA0F,EAAE,UAAU,YAAY,UAAU,aAAa,CAAC;AAAA,EAC/T,SAAS,cAAc,eAAe,mBAAmB,+EAA+E,4CAA4C,EAAE,UAAU,aAAa,CAAC;AAAA;AAAA,EAG9M,SAAS,oBAAoB,wBAAwB,sBAAsB,4EAA4E,iGAAiG,EAAE,UAAU,aAAa,UAAU,cAAc,OAAO,iJAAiJ,CAAC;AAAA,EAClc,SAAS,iBAAiB,uBAAuB,sBAAsB,kFAAkF,4CAA4C,EAAE,UAAU,aAAa,CAAC;AAAA,EAC/N,SAAS,cAAc,cAAc,sBAAsB,wEAAwE,wDAAwD,EAAE,UAAU,aAAa,CAAC;AAAA,EACrN,SAAS,gBAAgB,0BAA0B,sBAAsB,0EAA0E,mCAAmC,EAAE,UAAU,aAAa,CAAC;AAAA;AAAA,EAGhN,SAAS,qBAAqB,kBAAkB,eAAe,kCAAkC,oFAAoF,EAAE,UAAU,cAAc,OAAO,kKAA6J,CAAC;AAAA,EACpX,SAAS,eAAe,YAAY,eAAe,qEAAqE,+BAA+B,EAAE,UAAU,aAAa,CAAC;AAAA,EACjL,SAAS,YAAY,SAAS,eAAe,kEAAkE,gDAAgD,EAAE,UAAU,eAAe,CAAC;AAAA,EAC3L,SAAS,cAAc,WAAW,eAAe,uEAAuE,2BAA2B,EAAE,UAAU,eAAe,CAAC;AACjL;;;AC/EA,SAAS,QAAQ,IAAY,OAAe,UAAkB,WAAmB,YAAoB,UAAqF,CAAC,GAAgB;AACzM,SAAO,IAAI;AAAA,IACT;AAAA,IACA;AAAA,IACA,UAAU,QAAQ,YAAY;AAAA,IAC9B;AAAA,IACA;AAAA,IACA,GAAI,QAAQ,UAAU,SAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;AAAA,IAC9D,UAAU,CAAC,QAAQ,SAAS,CAAC;AAAA,IAC7B,QAAQ,QAAQ,YAAY,QAAQ,EAAE,SAAS,OAAO,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,iBAAiB,SAAS,MAAM,WAAW,CAAC,iBAAiB,EAAE;AAAA,EAC5J,CAAC;AACH;AAEO,IAAM,0BAAyC;AAAA;AAAA;AAAA;AAAA,EAIpD,QAAQ,oBAAoB,gBAAgB,mBAAmB,gFAAgF,wGAAwG;AAAA,EACvP,QAAQ,WAAW,uBAAuB,mBAAmB,yFAAyF,2DAA2D;AAAA,EACjN,QAAQ,YAAY,eAAe,mBAAmB,gEAAgE,uCAAuC;AAAA,EAC7J,QAAQ,gBAAgB,YAAY,mBAAmB,6EAA6E,uGAAuG;AAAA,EAC3O,QAAQ,iBAAiB,aAAa,mBAAmB,2FAA2F,uDAAuD;AAAA,EAC3M,QAAQ,cAAc,UAAU,mBAAmB,wEAAwE,sDAAsD;AAAA,EACjL,QAAQ,cAAc,UAAU,mBAAmB,kFAAkF,wCAAwC,EAAE,UAAU,eAAe,CAAC;AAAA,EACzM,QAAQ,sBAAsB,kCAAkC,mBAAmB,oEAAoE,6FAA6F;AAAA,EACpP,QAAQ,gBAAgB,uCAAuC,mBAAmB,4EAA4E,0CAA0C;AAAA,EACxM,QAAQ,cAAc,UAAU,mBAAmB,oFAAoF,sDAAsD;AAAA,EAC7L,QAAQ,eAAe,WAAW,mBAAmB,0EAA0E,iCAAiC;AAAA,EAChK,QAAQ,gBAAgB,YAAY,mBAAmB,4EAA4E,iCAAiC;AAAA,EACpK,QAAQ,gBAAgB,YAAY,mBAAmB,wEAAwE,0CAA0C;AAAA,EACzK,QAAQ,WAAW,kBAAkB,mBAAmB,4FAA4F,iCAAiC;AAAA;AAAA;AAAA;AAAA,EAKrL,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,CAAC,QAAQ,kIAAkI,CAAC;AAAA,IACtJ,QAAQ,EAAE,SAAS,iBAAiB,SAAS,MAAM,UAAU,UAAU;AAAA,EACzE,CAAC;AAAA,EACD,QAAQ,oBAAoB,0BAA0B,sBAAsB,sHAAsH,mDAAmD;AAAA,EACrP,QAAQ,kBAAkB,wBAAwB,sBAAsB,wGAAwG,yDAAyD;AAAA,EACzO,QAAQ,mBAAmB,6BAA6B,sBAAsB,qGAAqG,4BAA4B;AAAA,EAC/M,QAAQ,mBAAmB,sBAAsB,sBAAsB,8FAA8F,6CAA6C;AAAA,EAClN,QAAQ,qBAAqB,uBAAuB,sBAAsB,4EAA4E,8DAA8D;AAAA,EACpN,QAAQ,sBAAsB,8CAA8C,sBAAsB,8DAA8D,iEAAiE;AAAA,EACjO,QAAQ,sBAAsB,oCAAoC,sBAAsB,oFAAoF,qCAAqC;AAAA,EACjN,QAAQ,sBAAsB,0BAA0B,sBAAsB,+DAA+D,iEAAiE;AAAA,EAC9M,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,CAAC,MAAM,4FAA4F,CAAC,CAAC,UAAU,oDAAoD,GAAG,CAAC,mBAAmB,mBAAmB,GAAG,CAAC,mBAAmB,gBAAgB,CAAC,CAAC,CAAC;AAAA,IACjQ,QAAQ,EAAE,SAAS,OAAO,aAAa,CAAC,MAAM,EAAE;AAAA,EAClD,CAAC;AAAA;AAAA;AAAA;AAAA,EAKD,QAAQ,kBAAkB,uBAAuB,iBAAiB,oEAAoE,6EAA6E;AAAA,EACnN,QAAQ,oBAAoB,sBAAsB,iBAAiB,2KAA2K,2CAA2C;AAAA,EACzR,QAAQ,kBAAkB,+BAA+B,iBAAiB,6EAA6E,2FAA2F;AAAA,EAClP,QAAQ,mBAAmB,qBAAqB,iBAAiB,yEAAyE,sCAAsC;AAAA,EAChL,QAAQ,mBAAmB,wBAAwB,iBAAiB,+FAA+F,sEAAsE;AAC3O;;;ACjFA,SAAS,OAAO,MAAc,QAAwB;AACpD,MAAI,OAAO;AACX,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,YAAQ,KAAK,WAAW,IAAI,KAAK,MAAM,IAAI;AAC3C,WAAO,KAAK,KAAK,MAAM,QAAU,MAAM;AACvC,WAAO,OAAO,OAAO,GAAa,EAAE,SAAS,GAAG,GAAG;AAAA,EACrD;AACA,SAAO,IAAI,MAAM,GAAG,MAAM;AAC5B;AAiCO,SAAS,UAAU,UAA4B,CAAC,GAAW;AAChE,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,eAAe,QAAQ,gBAAgB;AAC7C,QAAM,QAAiC,CAAC;AAExC,MAAI,QAAQ,cAAc,OAAO;AAC/B,UAAM,WAAW,SAAS,OAAO,GAAG,OAAO,MAAM,EAAE,CAAC,IAAI,SAAS;AACjE,UAAM,KAAK,CAAC,OAAO,QAAQ,CAAC;AAE5B,UAAM,KAAK,CAAC,kBAAkB,SAAS,YAAY,QAAQ,eAAe,GAAG,SAAS,CAAC;AAAA,EACzF;AACA,MAAI,QAAQ,gBAAgB,MAAM;AAChC,UAAM,KAAK,CAAC,QAAQ,QAAQ,SAAS,OAAO,OAAO,GAAG,OAAO,MAAM,EAAE,CAAC,EAAE,CAAC;AACzE,UAAM,KAAK,CAAC,WAAW,OAAO,OAAO,GAAG,OAAO,OAAO,CAAC,CAAC,IAAI,SAAS,EAAE,CAAC;AAAA,EAC1E;AACA,MAAI,QAAQ,YAAY,MAAM;AAC5B,UAAM,KAAK;AAAA,MACT;AAAA,MACA;AAAA,IACF,CAAC;AACD,UAAM;AAAA,MAAK,CAAC,gBAAgB,4QAA4Q;AAAA,IACxS;AAAA,EACF;AACA,MAAI,QAAQ,eAAe,MAAM;AAC/B,UAAM,KAAK,CAAC,WAAW,GAAG,OAAO,GAAG,OAAO,MAAM,EAAE,CAAC,IAAI,YAAY,YAAY,OAAO,GAAG,OAAO,OAAO,EAAE,CAAC,EAAE,CAAC;AAC9G,UAAM,KAAK,CAAC,gBAAgB,GAAG,OAAO,GAAG,OAAO,OAAO,EAAE,CAAC,IAAI,YAAY,YAAY,OAAO,GAAG,OAAO,OAAO,EAAE,CAAC,EAAE,CAAC;AAAA,EACtH;AACA,MAAI,QAAQ,qBAAqB,MAAM;AACrC,UAAM,KAAK,CAAC,0BAA0B,kBAAkB,OAAO,GAAG,OAAO,MAAM,CAAC,CAAC,kCAAkC,CAAC;AACpH,UAAM,KAAK,CAAC,wBAAwB,KAAK,OAAO,GAAG,OAAO,MAAM,CAAC,CAAC,cAAc,OAAO,GAAG,OAAO,OAAO,EAAE,CAAC,EAAE,CAAC;AAAA,EAChH;AACA,MAAI,QAAQ,gBAAgB,OAAO;AACjC,UAAM,KAAK,CAAC,OAAO,OAAO,OAAO,GAAG,OAAO,OAAO,EAAE,CAAC,IAAI,OAAO,GAAG,OAAO,OAAO,EAAE,CAAC,EAAE,CAAC;AACvF,UAAM,KAAK,CAAC,aAAa,OAAO,GAAG,OAAO,QAAQ,EAAE,CAAC,CAAC;AAAA,EACxD;AACA,aAAW,CAAC,MAAM,KAAK,KAAK,QAAQ,SAAS,CAAC,EAAG,OAAM,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzE,SAAO,MAAM,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,GAAG,IAAI,IAAI,KAAK,EAAE,EAAE,KAAK,IAAI;AACnE;AAGO,SAAS,gBAAgB,UAAU,OAAe;AACvD,SAAO,UAAU,EAAE,SAAS,WAAW,OAAO,aAAa,KAAK,CAAC;AACnE;AAGO,SAAS,qBAAqB,UAAU,aAAqB;AAClE,SAAO,UAAU;AAAA,IACf;AAAA,IACA,WAAW;AAAA,IACX,aAAa;AAAA,IACb,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,kBAAkB;AAAA,IAClB,aAAa;AAAA,IACb,OAAO;AAAA,MACL,CAAC,QAAQ,GAAG,OAAO,GAAG,OAAO,QAAQ,CAAC,CAAC,WAAW;AAAA,MAClD,CAAC,UAAU,OAAO;AAAA,MAClB,CAAC,YAAY,KAAK;AAAA,IACpB;AAAA,EACF,CAAC;AACH;;;ACpGA,SAAS,KAAK,IAAY,OAAe,WAAmB,YAAoB,QAA2B,CAAC,GAAG,UAAiD,CAAC,GAAgB;AAC/K,SAAO,IAAI;AAAA,IACT;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA,GAAI,QAAQ,UAAU,SAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;AAAA,IAC9D,UAAU,CAAC,MAAM,WAAW,CAAC,CAAC,mBAAmB,UAAU,GAAG,GAAG,KAAK,CAAC,CAAC;AAAA,IACxE,QAAQ,EAAE,aAAa,CAAC,SAAS,QAAQ,UAAU,EAAE;AAAA,IACrD,MAAM,CAAC,qBAAqB;AAAA,EAC9B,CAAC;AACH;AAEO,IAAM,oBAAmC;AAAA;AAAA;AAAA;AAAA,EAI9C,KAAK,0BAA0B,mCAAmC,6FAA6F,8EAA8E,CAAC,CAAC,oBAAoB,cAAc,GAAG,CAAC,UAAU,sBAAsB,GAAG,CAAC,gBAAgB,IAAI,GAAG,CAAC,mBAAmB,cAAc,GAAG,CAAC,qBAAqB,OAAO,CAAC,GAAG,EAAE,OAAO,iKAAiK,CAAC;AAAA,EACjkB,KAAK,sBAAsB,4BAA4B,wCAAwC,gEAAgE,CAAC,CAAC,oBAAoB,eAAe,GAAG,CAAC,mBAAmB,4BAA4B,GAAG,CAAC,aAAa,QAAQ,GAAG,CAAC,aAAa,WAAW,CAAC,CAAC;AAAA,EAC9S,KAAK,eAAe,qBAAqB,wCAAwC,wDAAwD,CAAC,CAAC,kBAAkB,YAAY,GAAG,CAAC,mBAAmB,YAAY,GAAG,CAAC,qBAAqB,GAAG,GAAG,CAAC,OAAO,4CAA4C,CAAC,CAAC;AAAA,EACjS,KAAK,mBAAmB,6BAA6B,qBAAqB,yFAAyF,CAAC,CAAC,eAAe,qDAAqD,GAAG,CAAC,6BAA6B,IAAI,GAAG,CAAC,+BAA+B,OAAO,GAAG,CAAC,mBAAmB,cAAc,GAAG,CAAC,OAAO,0CAA0C,CAAC,GAAG,EAAE,SAAS,MAAM,CAAC;AAAA,EACxa,KAAK,cAAc,yBAAyB,0CAA0C,4CAA4C,CAAC,CAAC,mBAAmB,aAAa,GAAG,CAAC,gBAAgB,QAAQ,GAAG,CAAC,0BAA0B,IAAI,CAAC,CAAC;AAAA,EACpO,KAAK,eAAe,gDAAgD,yBAAyB,sDAAsD,CAAC,CAAC,eAAe,8CAA8C,GAAG,CAAC,uBAAuB,IAAI,GAAG,CAAC,mBAAmB,eAAe,CAAC,GAAG,EAAE,SAAS,MAAM,CAAC;AAAA,EAC7S,KAAK,0BAA0B,kCAAkC,oDAAoD,0EAA0E,CAAC,CAAC,aAAa,cAAc,GAAG,CAAC,mBAAmB,cAAc,CAAC,CAAC;AAAA;AAAA;AAAA;AAAA,EAKnQ,KAAK,uBAAuB,uBAAuB,yBAAyB,gEAAgE,CAAC,CAAC,cAAc,OAAO,CAAC,GAAG,EAAE,OAAO,8LAA8L,CAAC;AAAA,EAC/W,KAAK,mBAAmB,+BAA+B,gBAAgB,iDAAiD,CAAC,CAAC;AAAA,EAC1H,KAAK,oBAAoB,6BAA6B,qBAAqB,kFAAkF,CAAC,CAAC;AAAA,EAC/J,KAAK,mBAAmB,qDAAqD,YAAY,iDAAiD,CAAC,CAAC,oBAAoB,MAAM,GAAG,CAAC,gBAAgB,sCAAsC,GAAG,CAAC,kCAAkC,OAAO,CAAC,GAAG,EAAE,SAAS,MAAM,CAAC;AAAA,EACnS,KAAK,oBAAoB,4BAA4B,mBAAmB,8DAA8D,CAAC,CAAC;AAAA,EACxI,KAAK,uBAAuB,uDAAuD,mHAAmH,2FAA2F,CAAC,CAAC,aAAa,eAAe,GAAG,CAAC,mBAAmB,eAAe,GAAG,CAAC,qBAAqB,OAAO,GAAG,CAAC,oBAAoB,cAAc,CAAC,GAAG,EAAE,SAAS,OAAO,OAAO,8KAA8K,CAAC;AAAA,EACvnB,KAAK,wBAAwB,8CAA8C,eAAe,0FAA0F,CAAC,CAAC,aAAa,4CAA4C,GAAG,CAAC,mBAAmB,YAAY,CAAC,GAAG,EAAE,SAAS,MAAM,CAAC;AAAA,EACxS,KAAK,oBAAoB,mDAAmD,+BAA+B,+EAA+E,CAAC,CAAC,mBAAmB,0CAA0C,GAAG,CAAC,mBAAmB,gBAAgB,GAAG,CAAC,oBAAoB,KAAK,CAAC,GAAG,EAAE,SAAS,MAAM,CAAC;AAAA,EACnV,KAAK,aAAa,4BAA4B,cAAc,+CAA+C,CAAC,CAAC,qBAAqB,sBAAsB,GAAG,CAAC,mBAAmB,cAAc,CAAC,GAAG,EAAE,SAAS,MAAM,CAAC;AAAA;AAAA;AAAA;AAAA,EAKnN,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU;AAAA,MACR;AAAA,QACE,GAAG,QAAQ,iBAAiB,EAAE,QAAQ,qBAAqB,SAAS,GAAG,MAAM,wBAAwB,SAAS,wBAAwB,CAAC;AAAA,QACvI,SAAS;AAAA,UACP,GAAG,QAAQ,iBAAiB,EAAE,QAAQ,qBAAqB,SAAS,GAAG,MAAM,wBAAwB,SAAS,wBAAwB,CAAC,EAAE;AAAA,UACzI,CAAC,mBAAmB,uCAAuC;AAAA,UAC3D,CAAC,qBAAqB,OAAO;AAAA,UAC7B,CAAC,OAAO,qCAAqC;AAAA,QAC/C;AAAA,QACA,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,MAAM;AAAA,EAC3B,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU;AAAA,MACR;AAAA,QACE,SAAS,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,aAAa,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,+BAA+B,GAAG,CAAC,aAAa,aAAa,GAAG,CAAC,kBAAkB,aAAa,GAAG,CAAC,oBAAoB,aAAa,CAAC;AAAA,QAChP,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,iBAAiB,UAAU,eAAe,SAAS,KAAK;AAAA,IAC3E,MAAM,CAAC,UAAU;AAAA,EACnB,CAAC;AAAA;AAAA;AAAA;AAAA,EAKD,KAAK,mBAAmB,oCAAoC,6EAA6E,gEAAgE;AAAA,EACzM,KAAK,gCAAgC,6CAA6C,qHAAqH,2EAA2E;AAAA,EAClR,KAAK,0BAA0B,0CAA0C,4BAA4B,yEAAyE,CAAC,GAAG,EAAE,SAAS,MAAM,CAAC;AAAA,EACpM,KAAK,iBAAiB,0CAA0C,mBAAmB,oDAAoD,CAAC,GAAG,EAAE,SAAS,MAAM,CAAC;AAAA,EAC7J,KAAK,uBAAuB,kDAAkD,0FAA0F,0DAA0D;AAAA,EAClO,KAAK,kBAAkB,mCAAmC,4EAA4E,yDAAyD;AACjM;;;ACzFA,IAAM,OAAO;AAUb,SAAS,QAAQ,MAAmB,OAAe,YAAoB,UAA0B,CAAC,GAAG,OAA6B;AAChI,SAAO,MAAM;AAAA,IACX,IAAI,UAAU,MAAM,IAAI,CAAC;AAAA,IACzB;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;AAAA,IACvC,UAAU,CAAC,EAAE,GAAG,QAAQ,MAAM,EAAE,QAAQ,gBAAgB,IAAI,GAAG,GAAG,QAAQ,CAAC,GAAG,MAAM,IAAI,CAAC;AAAA,IACzF,QAAQ,EAAE,SAAS,OAAO,SAAS,CAAC,WAAW,OAAO,EAAE;AAAA,EAC1D,CAAC;AACH;AAEA,SAAS,MAAM,MAAsB;AACnC,SAAO,KAAK,QAAQ,sBAAsB,OAAO,EAAE,YAAY;AACjE;AAGA,SAAS,UAAU,MAAmB,OAAe,MAA0C;AAC7F,SAAO,KAAK;AAAA,IAAI,CAAC,aACf,MAAM;AAAA,MACJ,IAAI,UAAU,MAAM,IAAI,CAAC,IAAI,SAAS,MAAM;AAAA,MAC5C,OAAO,GAAG,KAAK,KAAK,SAAS,KAAK;AAAA,MAClC,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,UAAU,CAAC,EAAE,GAAG,QAAQ,MAAM,SAAS,OAAO,GAAG,GAAI,SAAS,SAAS,SAAY,EAAE,MAAM,SAAS,KAAK,IAAI,CAAC,EAAG,CAAC;AAAA,MAClH,QAAQ,EAAE,SAAS,OAAO,SAAS,CAAC,WAAW,OAAO,EAAE;AAAA,IAC1D,CAAC;AAAA,EACH;AACF;AAEA,IAAM,SAA8B;AAAA,EAClC;AAAA,IACE,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS,EAAE,MAAM,wBAAwB,SAAS,GAAG,IAAI,aAAa,QAAQ,qBAAqB,SAAS,EAAE;AAAA,EAChH;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS,EAAE,MAAM,wBAAwB,SAAS,GAAG,IAAI,WAAW,QAAQ,qBAAqB,OAAO,GAAG,kBAAkB,MAAM,SAAS,EAAE,aAAa,QAAQ,eAAe,gBAAgB,EAAE;AAAA,EACtM;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS,EAAE,MAAM,OAAO,QAAQ,qBAAqB,KAAK,GAAG,QAAQ,KAAK;AAAA,EAC5E;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS,EAAE,MAAM,YAAY,QAAQ,qBAAqB,QAAQ,GAAG,QAAQ,MAAM,YAAY,EAAE,MAAM,uBAAuB,eAAe,gCAAgC,EAAE;AAAA,EACjL;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS,EAAE,MAAM,aAAa,SAAS,GAAG,IAAI,aAAa,QAAQ,MAAM,QAAQ,qBAAqB,MAAM,GAAG,aAAa,qCAAqC,eAAe,IAAI;AAAA,EACtL;AACF;AAEA,IAAM,gBAAqC;AAAA,EACzC;AAAA,IACE,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS,EAAE,MAAM,wBAAwB,SAAS,GAAG,IAAI,KAAK,QAAQ,UAAU,EAAE,SAAS,UAAU,CAAC,GAAG,UAAU,MAAM,cAAc,EAAE,KAAK,KAAK,UAAU,KAAK,KAAK,KAAK,EAAE;AAAA,EAChL;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS,EAAE,MAAM,wBAAwB,SAAS,GAAG,IAAI,aAAa,QAAQ,UAAU,EAAE,SAAS,WAAW,CAAC,GAAG,UAAU,KAAK;AAAA,EACnI;AACF;AAEO,IAAM,sBAAqC;AAAA;AAAA;AAAA;AAAA,EAIhD,QAAQ,iBAAiB,4BAA4B,oGAAoG;AAAA,EACzJ,QAAQ,aAAa,sCAAsC,wHAAwH;AAAA,EACnL,QAAQ,eAAe,uBAAuB,yGAAyG;AAAA,EACvJ,QAAQ,kBAAkB,8BAA8B,+FAA+F;AAAA,EACvJ,QAAQ,eAAe,uBAAuB,mGAAmG;AAAA,EACjJ,QAAQ,WAAW,qBAAqB,6FAA6F;AAAA,EACrI,QAAQ,gBAAgB,wBAAwB,gFAAgF;AAAA,EAChI,QAAQ,kBAAkB,0BAA0B,8EAA8E;AAAA,EAClI;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA,EAAE,KAAK,MAAM,KAAK,KAAK;AAAA,IACvB;AAAA,EACF;AAAA,EACA,QAAQ,iBAAiB,kCAAkC,wFAAwF;AAAA;AAAA;AAAA;AAAA,EAKnJ,QAAQ,kBAAkB,0BAA0B,mHAAmH;AAAA,EACvK,QAAQ,cAAc,wBAAwB,uBAAuB;AAAA,EACrE,QAAQ,gBAAgB,uCAAuC,0FAA0F;AAAA,EACzJ,QAAQ,cAAc,iDAAiD,wFAAwF;AAAA,EAC/J,QAAQ,kBAAkB,0BAA0B,0FAA0F;AAAA;AAAA;AAAA;AAAA,EAK9I,QAAQ,aAAa,wBAAwB,kGAAkG;AAAA,EAC/I,QAAQ,aAAa,4BAA4B,gFAAgF;AAAA,EACjI,QAAQ,cAAc,0BAA0B,mGAAmG;AAAA,EACnJ,QAAQ,aAAa,iBAAiB,wHAA8G,CAAC,GAAG,qKAAqK;AAAA,EAC7T,QAAQ,cAAc,kBAAkB,mGAAmG;AAAA,EAC3I,QAAQ,WAAW,eAAe,wGAAwG;AAAA,EAC1I,QAAQ,iBAAiB,6BAA6B,qEAAqE,EAAE,KAAK,KAAK,CAAC;AAAA;AAAA;AAAA;AAAA,EAKxI,QAAQ,iBAAiB,kCAAkC,wGAAwG;AAAA,EACnK;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA,CAAC;AAAA,IACD;AAAA,EACF;AAAA,EACA,QAAQ,mBAAmB,uCAAuC,0FAA0F;AAAA,EAC5J,QAAQ,gBAAgB,0CAA0C,+CAA+C;AAAA,EACjH,QAAQ,eAAe,uBAAuB,qDAAqD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMnG;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA,CAAC;AAAA,IACD;AAAA,EACF;AAAA,EACA,QAAQ,aAAa,kCAAkC,qGAAqG;AAAA,EAC5J,QAAQ,iBAAiB,mCAAmC,sEAAsE;AAAA,EAClI,QAAQ,aAAa,kCAAkC,6FAA6F;AAAA;AAAA;AAAA;AAAA,EAKpJ,QAAQ,cAAc,gCAAgC,+GAA+G;AAAA,EACrK,QAAQ,WAAW,gCAAgC,sFAAsF;AAAA,EACzI,QAAQ,WAAW,4BAA4B,6DAA6D;AAAA,EAC5G,QAAQ,eAAe,kCAAkC,gEAAgE;AAAA,EACzH,QAAQ,kBAAkB,6BAA6B,6FAA6F;AAAA;AAAA;AAAA;AAAA,EAKpJ,GAAG,UAAU,iBAAiB,qBAAqB,MAAM;AAAA,EACzD,GAAG,UAAU,kBAAkB,sBAAsB,MAAM;AAAA,EAC3D,GAAG,UAAU,aAAa,mBAAmB,MAAM;AAAA,EACnD,GAAG,UAAU,eAAe,mBAAmB,MAAM;AAAA,EACrD,GAAG,UAAU,aAAa,oBAAoB,CAAC,GAAG,QAAQ,GAAG,aAAa,CAAC;AAAA,EAC3E,GAAG,UAAU,iBAAiB,qBAAqB,CAAC,GAAG,QAAQ,GAAG,aAAa,CAAC;AAAA,EAChF,GAAG,UAAU,mBAAmB,oBAAoB,aAAa;AAAA,EACjE,GAAG,UAAU,aAAa,mBAAmB,OAAO,MAAM,GAAG,CAAC,CAAC;AAAA,EAC/D,GAAG,UAAU,kBAAkB,sBAAsB,aAAa;AAAA;AAAA;AAAA;AAAA,EAKlE,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,QAAQ,iBAAiB,EAAE,QAAQ,qBAAqB,OAAO,GAAG,WAAW,MAAM,MAAM,wBAAwB,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,MAAM,YAAY,CAAC;AAAA,IAC5K,QAAQ,EAAE,SAAS,MAAM;AAAA,EAC3B,CAAC;AAAA,EACD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,QAAQ,iBAAiB,EAAE,MAAM,UAAU,SAAS,4BAA4B,eAAe,OAAO,CAAC,GAAG,MAAM,gBAAgB,CAAC;AAAA,IACjJ,QAAQ,EAAE,SAAS,MAAM;AAAA,EAC3B,CAAC;AAAA,EACD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,QAAQ,aAAa,EAAE,MAAM,SAAS,OAAO,yBAAyB,SAAS,GAAG,IAAI,iBAAiB,CAAC,GAAG,MAAM,kBAAkB,CAAC;AAAA,IACpJ,QAAQ,EAAE,SAAS,MAAM;AAAA,EAC3B,CAAC;AAAA,EACD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,QAAQ,iBAAiB,EAAE,MAAM,eAAe,QAAQ,qBAAqB,KAAK,GAAG,QAAQ,KAAK,CAAC,GAAG,MAAM,oBAAoB,CAAC;AAAA,IACjJ,QAAQ,EAAE,SAAS,MAAM;AAAA,EAC3B,CAAC;AAAA,EACD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,QAAQ,iBAAiB,EAAE,MAAM,QAAQ,QAAQ,0BAA0B,CAAC,GAAG,MAAM,sBAAsB,CAAC;AAAA,IAC5H,QAAQ,EAAE,SAAS,MAAM;AAAA,EAC3B,CAAC;AAAA,EACD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR,EAAE,GAAG,QAAQ,iBAAiB,EAAE,MAAM,cAAc,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,MAAM,mBAAmB,MAAM,EAAE;AAAA,MAC7G,EAAE,GAAG,QAAQ,iBAAiB,EAAE,MAAM,UAAU,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,MAAM,kBAAkB,MAAM,GAAG;AAAA,MACzG,EAAE,GAAG,QAAQ,iBAAiB,EAAE,MAAM,eAAe,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,MAAM,qBAAqB,MAAM,GAAG;AAAA,IACnH;AAAA,IACA,QAAQ,EAAE,SAAS,MAAM;AAAA,EAC3B,CAAC;AAAA,EACD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,CAAC,EAAE,GAAG,QAAQ,iBAAiB,EAAE,QAAQ,qBAAqB,OAAO,GAAG,SAAS,EAAE,aAAa,QAAQ,eAAe,SAAS,GAAG,kBAAkB,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAAA,IACnL,QAAQ,EAAE,SAAS,MAAM;AAAA,IACzB,MAAM,CAAC,eAAe;AAAA,EACxB,CAAC;AAAA,EACD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,QAAQ,iBAAiB,EAAE,MAAM,eAAe,aAAa,EAAE,eAAe,KAAK,KAAK,MAAM,GAAG,SAAS,GAAG,IAAI,iBAAiB,CAAC,GAAG,MAAM,iBAAiB,CAAC;AAAA,IAC9K,QAAQ,EAAE,SAAS,MAAM;AAAA,EAC3B,CAAC;AAAA,EACD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,QAAQ,kBAAkB,EAAE,KAAK,MAAM,KAAK,MAAM,QAAQ,gBAAgB,KAAK,EAAE,CAAC,GAAG,MAAM,IAAI,CAAC;AAAA,IAChH,QAAQ,EAAE,SAAS,OAAO,SAAS,UAAU;AAAA,EAC/C,CAAC;AACH;;;AClQA,SAAS,QAAQ,IAAY,OAAe,WAAmB,YAAoB,UAA8F,CAAC,GAAgB;AAChM,QAAM,UAAoB;AAAA,IACxB,CAAC,UAAU,uFAAuF;AAAA,IAClG,CAAC,mBAAmB,QAAQ,YAAY,gBAAgB;AAAA,IACxD,CAAC,mBAAmB,mBAAmB;AAAA,IACvC,CAAC,6BAA6B,GAAG;AAAA,IACjC,GAAI,QAAQ,SAAS,CAAC;AAAA,IACtB,GAAI,QAAQ,WAAW,QAAQ,CAAC,IAAK,CAAC,CAAC,UAAU,UAAU,EAAE,SAAS,IAAI,WAAW,KAAK,CAAC,CAAC,CAAC;AAAA,EAC/F;AACA,SAAO,MAAM;AAAA,IACX;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,GAAI,QAAQ,UAAU,SAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;AAAA,IAC9D,UAAU,CAAC,MAAM,WAAW,OAAO,CAAC;AAAA,IACpC,QAAQ,EAAE,SAAS,MAAM;AAAA,EAC3B,CAAC;AACH;AAEO,IAAM,kBAAiC;AAAA;AAAA;AAAA;AAAA,EAI5C;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,EAAE,OAAO,sJAAsJ;AAAA,EACjK;AAAA,EACA,QAAQ,yBAAyB,yCAAyC,mRAAmR,0FAA0F;AAAA,EACvb,QAAQ,oBAAoB,oCAAoC,+PAA+P,oEAAoE;AAAA,EACnY,QAAQ,wBAAwB,wCAAwC,uMAAuM,yEAAyE;AAAA,EACxV,QAAQ,iBAAiB,8BAA8B,8PAA8P,mEAAmE;AAAA,EACxX,QAAQ,sBAAsB,2BAA2B,6WAA6W,qFAAqF,EAAE,OAAO,kIAAkI,CAAC;AAAA,EACvoB,QAAQ,kBAAkB,kCAAkC,sPAAsP,0CAA0C;AAAA,EAC5V,QAAQ,gBAAgB,6BAA6B,4JAA4J,wEAAwE;AAAA,EACzR,QAAQ,gBAAgB,qCAAqC,+HAA+H,uCAAuC,EAAE,OAAO,6HAA6H,CAAC;AAAA,EAC1W,QAAQ,iBAAiB,8BAA8B,yIAAyI,wDAAwD;AAAA,EACxP,QAAQ,cAAc,mCAAmC,2LAA2L,oDAAoD;AAAA,EACxS,QAAQ,iBAAiB,sBAAsB,0IAA0I,2DAA2D;AAAA,EACpP,QAAQ,eAAe,sCAAsC,kLAAkL,4BAA4B,EAAE,OAAO,4IAA4I,CAAC;AAAA,EACja,QAAQ,gBAAgB,6BAA6B,mIAAmI,2BAA2B;AAAA,EACnN,QAAQ,gBAAgB,6BAA6B,oKAAoK,8FAAyF,EAAE,OAAO,8HAA8H,CAAC;AAAA,EAC1b,QAAQ,YAAY,yBAAyB,2IAA2I,gEAAgE,EAAE,UAAU,6BAA6B,CAAC;AAAA,EAClS,QAAQ,iBAAiB,8BAA8B,qLAAqL,yCAAyC,EAAE,UAAU,6BAA6B,CAAC;AAAA,EAC/T,QAAQ,kBAAkB,2BAA2B,4KAA4K,mFAAmF,EAAE,UAAU,0BAA0B,CAAC;AAAA,EAC3V,QAAQ,aAAa,0BAA0B,gOAAgO,gFAAgF,EAAE,UAAU,iBAAiB,CAAC;AAAA,EAC7X,QAAQ,UAAU,uBAAuB,+HAA+H,8CAA8C,EAAE,UAAU,6BAA6B,CAAC;AAAA;AAAA;AAAA;AAAA,EAKhQ,QAAQ,mBAAmB,+CAA+C,oIAAoI,wEAAwE,EAAE,OAAO,0GAA0G,CAAC;AAAA,EAC1Y,QAAQ,sBAAsB,6CAA6C,qLAAqL,6EAA6E;AAAA,EAC7U,QAAQ,sBAAsB,2CAA2C,wIAAwI,8DAA8D;AAAA,EAC/Q,QAAQ,mBAAmB,uCAAuC,0LAA0L,yDAAyD;AAAA,EACrT,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU;AAAA,MACR,MAAM,yKAAyK;AAAA,QAC7K,CAAC,UAAU,4EAA4E;AAAA,QACvF,CAAC,mBAAmB,gBAAgB;AAAA,QACpC,CAAC,mBAAmB,mBAAmB;AAAA,MACzC,CAAC;AAAA,IACH;AAAA,IACA,cACE;AAAA,IACF,QAAQ,EAAE,SAAS,iBAAiB,SAAS,MAAM,UAAU,eAAe;AAAA,IAC5E,MAAM,CAAC,cAAc,aAAa;AAAA,EACpC,CAAC;AAAA,EACD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR,MAAM,kJAAkJ;AAAA,QACtJ,CAAC,UAAU,iEAAiE;AAAA,QAC5E,CAAC,mBAAmB,OAAO;AAAA,QAC3B,CAAC,mBAAmB,eAAe;AAAA,MACrC,CAAC;AAAA,IACH;AAAA,IACA,QAAQ,EAAE,SAAS,MAAM;AAAA,EAC3B,CAAC;AAAA,EACD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU;AAAA,MACR,MAAM,gKAAgK;AAAA,QACpK,CAAC,UAAU,4EAA4E;AAAA,QACvF,CAAC,mBAAmB,yBAAyB;AAAA,QAC7C,CAAC,mBAAmB,mBAAmB;AAAA,MACzC,CAAC;AAAA,IACH;AAAA,IACA,QAAQ,EAAE,SAAS,MAAM;AAAA,EAC3B,CAAC;AAAA,EACD,QAAQ,oBAAoB,kDAAkD,sIAAsI,0EAA0E;AAAA,EAC9R,QAAQ,kBAAkB,mCAAmC,gKAAgK,uGAAuG;AAAA,EACpU,QAAQ,4BAA4B,8CAA8C,oKAAoK,kFAAkF,EAAE,QAAQ,OAAO,OAAO,uJAAuJ,CAAC;AAAA,EACxf,QAAQ,oBAAoB,sBAAsB,kLAAkL,oEAAoE,EAAE,UAAU,iBAAiB,CAAC;AAAA;AAAA;AAAA;AAAA,EAKtU;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,EAAE,OAAO,sTAAsT;AAAA,EACjU;AAAA,EACA,QAAQ,qBAAqB,oDAAoD,4JAA4J,+BAA+B;AAAA,EAC5Q,QAAQ,uBAAuB,4BAA4B,yJAAyJ,gDAAgD;AAAA,EACpQ,QAAQ,uBAAuB,2CAA2C,6JAA6J,+CAA+C,EAAE,OAAO,4JAA4J,CAAC;AAAA,EAC5b,QAAQ,sBAAsB,2BAA2B,qJAAqJ,iDAAiD;AAAA,EAC/P,QAAQ,qBAAqB,2CAA2C,4JAA4J,sCAAsC;AAAA,EAC1Q,QAAQ,uBAAuB,2DAA2D,8JAA8J,6DAA6D,EAAE,OAAO,8JAA8J,CAAC;AAAA,EAC7d,QAAQ,qBAAqB,6BAA6B,2JAA2J,4DAA4D;AAAA,EACjR,QAAQ,sBAAsB,qCAAqC,yJAAyJ,8FAA8F;AAAA,EAC1T,QAAQ,qBAAqB,mCAAmC,sJAAsJ,6DAA6D;AACrR;;;ACpIA,SAAS,oBAAoB,SAAmC;AAC9D,QAAM,UAAU,CAAC,GAAG,QAAQ,OAAO;AACnC,UAAQ,OAAO,GAAG,GAAG,CAAC,iBAAiB,WAAW,GAAG,CAAC,iBAAiB,YAAY,CAAC;AACpF,SAAO,EAAE,GAAG,SAAS,QAAQ;AAC/B;AAEA,IAAM,QAA2B;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGA,SAAS,SAAS,IAAY,OAAe,SAAqC,YAAiC;AACjH,SAAO,MAAM;AAAA,IACX;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,UAAU,CAAC,OAAO;AAAA,IAClB,QAAQ,EAAE,SAAS,WAAW,UAAU,GAAG,cAAc,OAAO,QAAQ,QAAQ;AAAA,EAClF,CAAC;AACH;AAEO,IAAM,cAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWxC,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU;AAAA,MACR;AAAA,QACE;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO,aAAa,gBAAgB;AAAA,EACzD,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO,aAAa,gBAAgB;AAAA,EACzD,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMD,SAAS,kBAAkB,4BAA4B,QAAQ,eAAe,GAAG,oEAAoE;AAAA,EACrJ,SAAS,gBAAgB,uBAAuB,QAAQ,WAAW,GAAG,uBAAuB;AAAA,EAC7F,SAAS,gBAAgB,uBAAuB,QAAQ,aAAa,GAAG,uBAAuB;AAAA,EAC/F,SAAS,gBAAgB,uBAAuB,QAAQ,aAAa,GAAG,iEAAiE;AAAA,EACzI,SAAS,mBAAmB,0BAA0B,QAAQ,gBAAgB,GAAG,oDAAoD;AAAA,EACrI,SAAS,iBAAiB,uCAAuC,QAAQ,cAAc,GAAG,uBAAuB;AAAA,EACjH,SAAS,gBAAgB,wBAAwB,QAAQ,WAAW,GAAG,yDAAyD;AAAA;AAAA;AAAA;AAAA,EAKhI,SAAS,cAAc,yBAAyB,QAAQ,WAAW,GAAG,uBAAuB;AAAA,EAC7F,SAAS,kBAAkB,yBAAyB,QAAQ,eAAe,GAAG,uDAAuD;AAAA,EACrI,SAAS,oBAAoB,uCAAuC,QAAQ,iBAAiB,GAAG,0EAA0E;AAAA,EAE1K,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,QAAQ,iBAAiB,EAAE,MAAM,uBAAuB,SAAS,0BAA0B,CAAC,CAAC;AAAA,IACxG,QAAQ,EAAE,SAAS,WAAW,UAAU,GAAG,QAAQ,QAAQ;AAAA,EAC7D,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,CAAC,EAAE,GAAG,QAAQ,aAAa,EAAE,MAAM,MAAM,CAAC,GAAG,MAAM,YAAY,CAAC;AAAA,IAC1E,QAAQ,EAAE,SAAS,WAAW,UAAU,GAAG,cAAc,CAAC,kBAAkB,GAAG,QAAQ,QAAQ;AAAA,EACjG,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,QAAQ,iBAAiB,EAAE,MAAM,cAAc,CAAC,CAAC;AAAA,IAC5D,QAAQ,EAAE,SAAS,WAAW,UAAU,GAAG,QAAQ,QAAQ;AAAA,EAC7D,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE;AAAA,QACA,CAAC,CAAC,UAAU,iEAAiE,GAAG,CAAC,mBAAmB,gBAAgB,GAAG,CAAC,mBAAmB,mBAAmB,CAAC;AAAA,MACjK;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,WAAW,UAAU,IAAI,QAAQ,CAAC,SAAS,OAAO,KAAK,EAAE;AAAA,EAC9E,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE;AAAA,QACA,CAAC,CAAC,UAAU,uFAAuF,GAAG,CAAC,mBAAmB,gBAAgB,GAAG,CAAC,mBAAmB,mBAAmB,GAAG,CAAC,kBAAkB,MAAM,GAAG,CAAC,kBAAkB,UAAU,GAAG,CAAC,kBAAkB,UAAU,CAAC;AAAA,MACnR;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,CAAC,WAAW,eAAe,GAAG,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,WAAW,EAAE;AAAA,IACvG,OAAO;AAAA,EACT,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE;AAAA,QACA,CAAC,CAAC,UAAU,4EAA4E,GAAG,CAAC,mBAAmB,gBAAgB,GAAG,CAAC,mBAAmB,eAAe,CAAC;AAAA,MACxK;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,CAAC,WAAW,eAAe,GAAG,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,WAAW,EAAE;AAAA,IACvG,OAAO;AAAA,EACT,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE;AAAA,QACA,CAAC,CAAC,UAAU,iEAAiE,GAAG,CAAC,mBAAmB,gBAAgB,GAAG,CAAC,mBAAmB,mBAAmB,CAAC;AAAA,MACjK;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,CAAC,WAAW,eAAe,GAAG,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,WAAW,EAAE;AAAA,EACzG,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU;AAAA,MACR;AAAA,QACE;AAAA,QACA,CAAC,CAAC,UAAU,iEAAiE,GAAG,CAAC,mBAAmB,gBAAgB,GAAG,CAAC,mBAAmB,mBAAmB,CAAC;AAAA,MACjK;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,CAAC,WAAW,eAAe,GAAG,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,WAAW,EAAE;AAAA,EACzG,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE;AAAA,QACA,CAAC,CAAC,UAAU,4EAA4E,GAAG,CAAC,mBAAmB,OAAO,GAAG,CAAC,mBAAmB,mBAAmB,GAAG,CAAC,kBAAkB,MAAM,GAAG,CAAC,kBAAkB,UAAU,GAAG,CAAC,kBAAkB,UAAU,CAAC;AAAA,MAC/P;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO,UAAU,CAAC,WAAW,cAAc,GAAG,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,WAAW,EAAE;AAAA,IACvH,MAAM,CAAC,YAAY;AAAA,EACrB,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE;AAAA,QACA,CAAC,CAAC,UAAU,iEAAiE,GAAG,CAAC,mBAAmB,OAAO,GAAG,CAAC,mBAAmB,mBAAmB,CAAC;AAAA,MACxJ;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,WAAW,EAAE;AAAA,EAClF,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU;AAAA,MACR;AAAA,QACE,SAAS;AAAA,UACP,CAAC,QAAQ,cAAc;AAAA,UACvB,CAAC,cAAc,sEAAsE;AAAA,UACrF,CAAC,UAAU,uFAAuF;AAAA,UAClG,CAAC,mBAAmB,gBAAgB;AAAA,UACpC,CAAC,mBAAmB,mBAAmB;AAAA,UACvC,CAAC,cAAc,YAAY;AAAA,UAC3B,CAAC,6BAA6B,GAAG;AAAA,UACjC,CAAC,kBAAkB,UAAU;AAAA,UAC7B,CAAC,kBAAkB,UAAU;AAAA,UAC7B,CAAC,kBAAkB,MAAM;AAAA,UACzB,CAAC,kBAAkB,IAAI;AAAA,QACzB;AAAA,QACA,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,WAAW,UAAU,GAAG,QAAQ,QAAQ;AAAA,EAC7D,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE,SAAS;AAAA,UACP,CAAC,QAAQ,cAAc;AAAA,UACvB,CAAC,cAAc,sEAAsE;AAAA,UACrF,CAAC,UAAU,iEAAiE;AAAA,UAC5E,CAAC,mBAAmB,WAAW;AAAA,UAC/B,CAAC,mBAAmB,mBAAmB;AAAA,UACvC,CAAC,cAAc,YAAY;AAAA,UAC3B,CAAC,6BAA6B,GAAG;AAAA,UACjC,CAAC,kBAAkB,UAAU;AAAA,UAC7B,CAAC,kBAAkB,UAAU;AAAA,UAC7B,CAAC,kBAAkB,MAAM;AAAA,UACzB,CAAC,kBAAkB,IAAI;AAAA,QACzB;AAAA,QACA,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,WAAW,UAAU,IAAI,QAAQ,CAAC,SAAS,OAAO,KAAK,EAAE;AAAA,EAC9E,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,QAAQ,iBAAiB,EAAE,gBAAgB,iBAAiB,CAAC,CAAC;AAAA,IACzE,QAAQ,EAAE,SAAS,WAAW,UAAU,GAAG,QAAQ,QAAQ;AAAA,EAC7D,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU;AAAA,MACR;AAAA,QACE,SAAS;AAAA,UACP,CAAC,QAAQ,cAAc;AAAA,UACvB,CAAC,cAAc,YAAY;AAAA,UAC3B,CAAC,aAAa,mEAAmE;AAAA,UACjF,CAAC,oBAAoB,IAAI;AAAA,UACzB,CAAC,sBAAsB,WAAW;AAAA,UAClC,CAAC,6BAA6B,GAAG;AAAA,UACjC,CAAC,cAAc,uHAAuH;AAAA,UACtI,CAAC,UAAU,iEAAiE;AAAA,UAC5E,CAAC,kBAAkB,MAAM;AAAA,UACzB,CAAC,kBAAkB,UAAU;AAAA,UAC7B,CAAC,kBAAkB,IAAI;AAAA,UACvB,CAAC,kBAAkB,UAAU;AAAA,UAC7B,CAAC,mBAAmB,mBAAmB;AAAA,UACvC,CAAC,mBAAmB,gBAAgB;AAAA,QACtC;AAAA,QACA,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,aAAa,YAAY,EAAE;AAAA,IAC9F,MAAM,CAAC,YAAY;AAAA,EACrB,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,WAAW,QAAQ,iBAAiB,EAAE,MAAM,wBAAwB,SAAS,wBAAwB,CAAC,GAAG;AAAA,MACjH;AAAA,MAAK;AAAA,MAAa;AAAA,MAAe;AAAA,MAAgB;AAAA,MAAU;AAAA,MAAgB;AAAA,MAC3E;AAAA,MAAgB;AAAA,MAAW;AAAA,MAAe;AAAA,MAAa;AAAA,MAAgB;AAAA,MAAY;AAAA,IACrF,CAAC;AAAA,IACD,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,aAAa,YAAY,EAAE;AAAA,IAC9F,MAAM,CAAC,YAAY;AAAA,EACrB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU;AAAA,MACR;AAAA,QACE,SAAS;AAAA,UACP,CAAC,QAAQ,cAAc;AAAA,UACvB,CAAC,UAAU,+EAA+E;AAAA,UAC1F,CAAC,mBAAmB,uBAAuB;AAAA,UAC3C,CAAC,mBAAmB,IAAI;AAAA,UACxB,CAAC,cAAc,uDAAuD;AAAA,QACxE;AAAA,QACA,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,aAAa,YAAY,EAAE;AAAA,IAC9F,MAAM,CAAC,cAAc,eAAe;AAAA,EACtC,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE,SAAS;AAAA,UACP,CAAC,QAAQ,cAAc;AAAA,UACvB,CAAC,cAAc,uBAAuB;AAAA,UACtC,CAAC,UAAU,qEAAqE;AAAA,UAChF,CAAC,mBAAmB,mBAAmB;AAAA,UACvC,CAAC,mBAAmB,UAAU;AAAA,QAChC;AAAA,QACA,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,aAAa,YAAY,EAAE;AAAA,IAC9F,MAAM,CAAC,cAAc,eAAe;AAAA,EACtC,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,CAAC,QAAQ,gBAAgB,CAAC;AAAA,IACpC,QAAQ,EAAE,SAAS,WAAW,UAAU,GAAG,QAAQ,QAAQ;AAAA,IAC3D,MAAM,CAAC,eAAe;AAAA,EACxB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE,SAAS;AAAA,UACP,CAAC,UAAU,kDAAkD;AAAA,UAC7D,CAAC,mBAAmB,OAAO;AAAA,UAC3B,CAAC,cAAc,uEAAuE;AAAA,UACtF,CAAC,mBAAmB,eAAe;AAAA,UACnC,CAAC,QAAQ,cAAc;AAAA,UACvB,CAAC,cAAc,YAAY;AAAA,QAC7B;AAAA,QACA,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,aAAa,YAAY,EAAE;AAAA,IAC9F,OAAO;AAAA,IACP,MAAM,CAAC,YAAY;AAAA,EACrB,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE;AAAA,QACA,CAAC,CAAC,UAAU,iEAAiE,GAAG,CAAC,mBAAmB,cAAc,GAAG,CAAC,mBAAmB,eAAe,CAAC;AAAA,MAC3J;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,aAAa,YAAY,EAAE;AAAA,EAChG,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE;AAAA,QACA,CAAC,CAAC,UAAU,iEAAiE,GAAG,CAAC,mBAAmB,OAAO,GAAG,CAAC,mBAAmB,eAAe,CAAC;AAAA,MACpJ;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,aAAa,YAAY,EAAE;AAAA,EAChG,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE;AAAA,QACA,CAAC,CAAC,UAAU,iEAAiE,GAAG,CAAC,mBAAmB,OAAO,GAAG,CAAC,mBAAmB,mBAAmB,CAAC;AAAA,MACxJ;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,aAAa,YAAY,EAAE;AAAA,EAChG,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,WAAW,QAAQ,iBAAiB,EAAE,MAAM,wBAAwB,QAAQ,yBAAyB,SAAS,gCAAgC,CAAC,GAAG;AAAA,MAC1J;AAAA,MAAK;AAAA,MAAa;AAAA,MAAgB;AAAA,MAAa;AAAA,MAAe;AAAA,MAAgB;AAAA,MAAW;AAAA,MAAa;AAAA,MAAe;AAAA,IACvH,CAAC;AAAA,IACD,QAAQ,EAAE,SAAS,WAAW,UAAU,IAAI,cAAc,CAAC,WAAW,iBAAiB,mBAAmB,GAAG,QAAQ,QAAQ;AAAA,EAC/H,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,MAAM,KAAK,EAAE,QAAQ,GAAG,GAAG,CAAC,GAAG,WAAW;AAAA,MAClD,GAAG,QAAQ,iBAAiB,EAAE,QAAQ,gBAAgB,CAAC;AAAA,MACvD,MAAM,aAAa,QAAQ,CAAC;AAAA,MAC5B,MAAM,QAAQ;AAAA,IAChB,EAAE;AAAA,IACF,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,aAAa,YAAY,EAAE;AAAA,IAC9F,MAAM,CAAC,YAAY;AAAA,EACrB,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,WAAW,QAAQ,kBAAkB,EAAE,MAAM,wBAAwB,QAAQ,cAAc,SAAS,4BAA4B,CAAC,GAAG;AAAA,MAC5I;AAAA,MAAe;AAAA,MAAiB;AAAA,MAAgB;AAAA,MAAoB;AAAA,MAAoB;AAAA,MACxF;AAAA,MAAqB;AAAA,MAAuB;AAAA,MAAa;AAAA,MAAmB;AAAA,MAAoB;AAAA,IAClG,CAAC;AAAA,IACD,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,aAAa,YAAY,EAAE;AAAA,IAC9F,MAAM,CAAC,YAAY;AAAA,EACrB,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR,EAAE,GAAG,QAAQ,iBAAiB,EAAE,MAAM,wBAAwB,QAAQ,eAAe,SAAS,6BAA6B,CAAC,GAAG,QAAQ,QAAQ,MAAM,SAAS;AAAA,IAChK;AAAA,IACA,QAAQ,EAAE,SAAS,WAAW,UAAU,GAAG,QAAQ,CAAC,SAAS,OAAO,EAAE;AAAA,EACxE,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,oBAAoB,QAAQ,iBAAiB,EAAE,MAAM,YAAY,QAAQ,eAAe,CAAC,CAAC,CAAC;AAAA,IACtG,QAAQ,EAAE,SAAS,WAAW,UAAU,GAAG,QAAQ,QAAQ;AAAA,EAC7D,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU;AAAA,MACR;AAAA,QACE,SAAS;AAAA,UACP,CAAC,QAAQ,cAAc;AAAA,UACvB,CAAC,cAAc,YAAY;AAAA,UAC3B,CAAC,cAAc,YAAY,eAAe,CAAC;AAAA,UAC3C,CAAC,UAAU,iEAAiE;AAAA,UAC5E,CAAC,mBAAmB,eAAe;AAAA,UACnC,CAAC,mBAAmB,gBAAgB;AAAA,UACpC,CAAC,OAAO,0CAA0C;AAAA,UAClD,CAAC,mBAAmB,YAAY;AAAA,QAClC;AAAA,QACA,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,aAAa,YAAY,EAAE;AAAA,IAC9F,MAAM,CAAC,YAAY;AAAA,EACrB,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE,SAAS;AAAA,UACP,CAAC,QAAQ,cAAc;AAAA,UACvB,CAAC,mBAAmB,MAAM;AAAA,UAC1B,CAAC,UAAU,iEAAiE;AAAA,UAC5E,CAAC,cAAc,YAAY,eAAe,CAAC;AAAA,UAC3C,CAAC,mBAAmB,gBAAgB;AAAA,UACpC,CAAC,OAAO,eAAe;AAAA,UACvB,CAAC,mBAAmB,aAAa;AAAA,QACnC;AAAA,QACA,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,aAAa,YAAY,EAAE;AAAA,IAC9F,OAAO;AAAA,IACP,MAAM,CAAC,YAAY;AAAA,EACrB,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU;AAAA,MACR;AAAA,QACE,GAAG,QAAQ,eAAe;AAAA,QAC1B,aAAa;AAAA,QACb,SAAS,QAAQ,eAAe,EAAE,QAAQ,OAAO,CAAC,CAAC,IAAI,MAAM,KAAK,YAAY,MAAM,YAAY;AAAA,MAClG;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,WAAW,UAAU,GAAG,cAAc,CAAC,cAAc,GAAG,QAAQ,QAAQ;AAAA,EAC7F,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU;AAAA,MACR,EAAE,GAAG,QAAQ,WAAW,GAAG,IAAI,eAAe,MAAM,EAAE;AAAA,MACtD,EAAE,GAAG,QAAQ,eAAe,GAAG,IAAI,eAAe,MAAM,IAAI;AAAA,MAC5D,EAAE,GAAG,QAAQ,iBAAiB,GAAG,IAAI,eAAe,MAAM,IAAI;AAAA,MAC9D,EAAE,GAAG,QAAQ,WAAW,GAAG,IAAI,eAAe,MAAM,IAAI;AAAA,MACxD,EAAE,GAAG,QAAQ,eAAe,GAAG,IAAI,eAAe,MAAM,IAAI;AAAA,MAC5D,EAAE,GAAG,QAAQ,eAAe,GAAG,IAAI,eAAe,MAAM,KAAM;AAAA,IAChE;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC,SAAS,OAAO,OAAO,SAAS,aAAa,YAAY,EAAE;AAAA,IAC9F,MAAM,CAAC,YAAY;AAAA,EACrB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,CAAC,EAAE,GAAG,QAAQ,iBAAiB,EAAE,MAAM,wBAAwB,SAAS,wBAAwB,CAAC,GAAG,MAAM,gBAAgB,CAAC;AAAA,IACrI,QAAQ,EAAE,SAAS,WAAW,UAAU,IAAI,QAAQ,CAAC,SAAS,OAAO,KAAK,EAAE;AAAA,IAC5E,MAAM,CAAC,YAAY;AAAA,EACrB,CAAC;AAAA,EACD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,CAAC,EAAE,GAAG,QAAQ,iBAAiB,EAAE,MAAM,wBAAwB,SAAS,4BAA4B,CAAC,GAAG,MAAM,kCAAkC,CAAC;AAAA;AAAA;AAAA;AAAA,IAI3J,QAAQ,EAAE,SAAS,WAAW,UAAU,IAAI,QAAQ,CAAC,SAAS,OAAO,OAAO,YAAY,EAAE;AAAA,IAC1F,MAAM,CAAC,YAAY;AAAA,EACrB,CAAC;AAAA,EACD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU;AAAA,MACR,EAAE,GAAG,QAAQ,iBAAiB,EAAE,MAAM,YAAY,QAAQ,4BAA4B,CAAC,GAAG,MAAM,oBAAoB,MAAM,EAAE;AAAA,MAC5H,EAAE,GAAG,QAAQ,iBAAiB,EAAE,MAAM,wBAAwB,QAAQ,6BAA6B,SAAS,wCAAwC,YAAY,EAAE,MAAM,sBAAsB,EAAE,CAAC,GAAG,MAAM,mBAAmB,MAAM,KAAM;AAAA,IAC3O;AAAA,IACA,QAAQ,EAAE,SAAS,CAAC,WAAW,OAAO,GAAG,UAAU,IAAI,WAAW,CAAC,oBAAoB,GAAG,QAAQ,CAAC,SAAS,OAAO,KAAK,EAAE;AAAA,EAC5H,CAAC;AACH;;;AC/pBO,IAAM,uBAAsC;AAAA;AAAA;AAAA;AAAA,EAIjD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,MAAM,KAAK,EAAE,QAAQ,GAAG,GAAG,CAAC,GAAG,WAAW,EAAE,GAAG,MAAM,iBAAiB,GAAG,MAAM,YAAY,MAAM,QAAQ,KAAQ,IAAI,YAAY,EAAE;AAAA,IAC7I,QAAQ,EAAE,aAAa,CAAC,SAAS,QAAQ,UAAU,EAAE;AAAA,IACrD,MAAM,CAAC,qBAAqB;AAAA,EAC9B,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,MAAM,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,WAAW,EAAE,GAAG,MAAM,uBAAuB,GAAG,MAAM,YAAY,MAAM,QAAQ,MAAQ,IAAI,YAAY,EAAE;AAAA,IAClJ,QAAQ,EAAE,aAAa,CAAC,SAAS,QAAQ,UAAU,EAAE;AAAA,IACrD,MAAM,CAAC,qBAAqB;AAAA,EAC9B,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,MAAM,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,WAAW,EAAE,GAAG,MAAM,4CAA4C,GAAG,MAAM,YAAY,MAAM,QAAQ,KAAQ,IAAI,YAAY,EAAE;AAAA,IACvK,QAAQ,EAAE,aAAa,CAAC,SAAS,QAAQ,UAAU,EAAE;AAAA,IACrD,MAAM,CAAC,qBAAqB;AAAA,EAC9B,CAAC;AAAA;AAAA;AAAA;AAAA,EAKD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU;AAAA,MACR;AAAA,QACE,SAAS;AAAA,UACP,CAAC,QAAQ,kBAAkB;AAAA,UAC3B,CAAC,UAAU,kBAAkB;AAAA,UAC7B,CAAC,iBAAiB,iBAAiB;AAAA,UACnC,CAAC,cAAc,2EAA2E;AAAA,UAC1F,CAAC,mBAAmB,aAAa;AAAA,UACjC,CAAC,mBAAmB,qCAAqC;AAAA,QAC3D;AAAA,QACA,MAAM;AAAA,QACN,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,aAAa,CAAC,SAAS,QAAQ,UAAU,EAAE;AAAA,IACrD,MAAM,CAAC,qBAAqB;AAAA,EAC9B,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE,SAAS,CAAC,CAAC,QAAQ,kBAAkB,GAAG,CAAC,UAAU,kBAAkB,GAAG,CAAC,cAAc,QAAQ,GAAG,CAAC,mBAAmB,eAAe,CAAC;AAAA,QACtI,MAAM;AAAA,QACN,UAAU;AAAA,QACV,aAAa;AAAA,QACb,IAAI;AAAA,MACN;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,aAAa,CAAC,SAAS,QAAQ,UAAU,EAAE;AAAA,IACrD,MAAM,CAAC,qBAAqB;AAAA,EAC9B,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU;AAAA,MACR;AAAA,QACE,SAAS;AAAA,UACP,CAAC,QAAQ,cAAc;AAAA,UACvB,CAAC,UAAU,6BAA6B;AAAA,UACxC,CAAC,gBAAgB,iCAAiC;AAAA,UAClD,CAAC,oBAAoB,0BAA0B;AAAA,UAC/C,CAAC,cAAc,gDAAgD;AAAA,QACjE;AAAA,QACA,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,aAAa,CAAC,SAAS,QAAQ,UAAU,EAAE;AAAA,IACrD,MAAM,CAAC,qBAAqB;AAAA,EAC9B,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE,SAAS,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,yBAAyB,GAAG,CAAC,gBAAgB,kBAAkB,GAAG,CAAC,kBAAkB,MAAM,GAAG,CAAC,UAAU,KAAK,CAAC;AAAA,QAClK,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,aAAa,CAAC,SAAS,QAAQ,UAAU,EAAE;AAAA,IACrD,MAAM,CAAC,qBAAqB;AAAA,EAC9B,CAAC;AAAA;AAAA;AAAA;AAAA,EAKD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU;AAAA,MACR;AAAA,QACE,GAAG,QAAQ,iBAAiB,EAAE,MAAM,wBAAwB,SAAS,gCAAgC,CAAC;AAAA,QACtG,SAAS;AAAA,UACP,GAAG,QAAQ,iBAAiB,EAAE,MAAM,wBAAwB,SAAS,gCAAgC,CAAC,EAAE,QAAQ,OAAO,CAAC,CAAC,IAAI,MAAM,SAAS,gBAAgB;AAAA,UAC5J,CAAC,eAAe,UAAU;AAAA,UAC1B,CAAC,WAAW,UAAU;AAAA,QACxB;AAAA,QACA,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO,aAAa,CAAC,SAAS,QAAQ,UAAU,EAAE;AAAA,EACvE,CAAC;AAAA,EACD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,QAAQ,iBAAiB,EAAE,MAAM,MAAM,CAAC,GAAG,MAAM,oBAAoB,CAAC;AAAA,IACtF,QAAQ,EAAE,SAAS,WAAW,UAAU,IAAI,aAAa,CAAC,SAAS,QAAQ,UAAU,EAAE;AAAA,EACzF,CAAC;AAAA;AAAA;AAAA;AAAA,EAKD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,CAAC,EAAE,GAAG,QAAQ,WAAW,GAAG,IAAI,cAAc,CAAC;AAAA,IACzD,QAAQ,EAAE,SAAS,OAAO,aAAa,CAAC,SAAS,QAAQ,UAAU,EAAE;AAAA,IACrE,MAAM,CAAC,YAAY;AAAA,EACrB,CAAC;AAAA,EACD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,QAAQ,WAAW,GAAG,IAAI,cAAc,CAAC;AAAA,IACzD,QAAQ,EAAE,SAAS,OAAO,aAAa,CAAC,SAAS,QAAQ,UAAU,EAAE;AAAA,IACrE,MAAM,CAAC,YAAY;AAAA,EACrB,CAAC;AAAA,EACD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,MAAM,KAAK,EAAE,QAAQ,GAAG,GAAG,CAAC,GAAG,WAAW;AAAA,MAClD,GAAG,QAAQ,QAAQ,MAAM,IAAI,kBAAkB,QAAQ,MAAM,IAAI,gBAAgB,gBAAgB;AAAA,MACjG,MAAM,aAAc,QAAQ,KAAM,CAAC;AAAA,MACnC,IAAI;AAAA,MACJ,MAAM,QAAQ;AAAA,IAChB,EAAE;AAAA,IACF,QAAQ,EAAE,SAAS,OAAO,aAAa,CAAC,SAAS,QAAQ,UAAU,EAAE;AAAA,IACrE,MAAM,CAAC,YAAY;AAAA,EACrB,CAAC;AAAA;AAAA;AAAA;AAAA,EAKD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU;AAAA,MACR;AAAA,QACE,SAAS,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,aAAa,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,yBAAyB,CAAC;AAAA,QACpI,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,iBAAiB,UAAU,eAAe,SAAS,KAAK;AAAA,IAC3E,MAAM,CAAC,UAAU;AAAA,EACnB,CAAC;AACH;;;AC1NO,IAAM,mBAAkC;AAAA,EAC7C,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,CAAC,UAAU;AAAA,IACrB,UAAU,CAAC,EAAE,GAAG,QAAQ,eAAe,GAAG,IAAI,gBAAgB,CAAC;AAAA,IAC/D,QAAQ,EAAE,SAAS,iBAAiB,SAAS,MAAM,WAAW,CAAC,iBAAiB,EAAE;AAAA,EACpF,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,CAAC,mBAAmB;AAAA,IAC9B,UAAU,CAAC,EAAE,GAAG,MAAM,wBAAwB,GAAG,IAAI,cAAc,CAAC;AAAA,IACpE,QAAQ,EAAE,SAAS,iBAAiB,UAAU,eAAe,SAAS,MAAM,WAAW,CAAC,mBAAmB,iBAAiB,EAAE;AAAA,EAChI,CAAC;AAAA,EACD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,CAAC,mBAAmB;AAAA,IAC9B,UAAU,CAAC,EAAE,GAAG,QAAQ,gBAAgB,GAAG,IAAI,cAAc,CAAC;AAAA,IAC9D,QAAQ,EAAE,SAAS,OAAO,WAAW,CAAC,iBAAiB,EAAE;AAAA,IACzD,MAAM,CAAC,YAAY;AAAA,EACrB,CAAC;AAAA,EAED,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,WAAW;AAAA,IACX,UAAU,CAAC,QAAQ,eAAe,CAAC;AAAA,IACnC,QAAQ,EAAE,WAAW,CAAC,WAAW,GAAG,aAAa,CAAC,SAAS,QAAQ,UAAU,EAAE;AAAA,EACjF,CAAC;AAAA,EACD,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,WAAW;AAAA,IACX,UAAU,CAAC,QAAQ,eAAe,CAAC;AAAA,IACnC,QAAQ,EAAE,SAAS,SAAS,SAAS,MAAM,WAAW,CAAC,WAAW,GAAG,QAAQ,CAAC,SAAS,OAAO,KAAK,EAAE;AAAA,EACvG,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,WAAW;AAAA,IACX,UAAU,CAAC,MAAM,+GAA+G,CAAC;AAAA;AAAA;AAAA,IAGjI,QAAQ,EAAE,SAAS,iBAAiB,UAAU,cAAc,SAAS,MAAM,SAAS,WAAW;AAAA,IAC/F,MAAM,CAAC,YAAY;AAAA,EACrB,CAAC;AACH;;;AC/EA,SAAS,OACP,IACA,OACA,SACA,YACA,UAAkO,CAAC,GACtN;AACb,QAAM,UAAuB;AAAA,IAC3B,SAAS,CAAC,CAAC,QAAQ,cAAc,GAAG,GAAG,OAAO;AAAA,IAC9C,UAAU;AAAA,IACV,aAAa;AAAA,IACb,GAAI,QAAQ,SAAS,SAAY,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC;AAAA,IAC3D,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAAA,EACnE;AACA,SAAO,IAAI;AAAA,IACT;AAAA,IACA;AAAA,IACA,UAAU,QAAQ,YAAY;AAAA,IAC9B,UAAU;AAAA,IACV;AAAA,IACA,GAAI,QAAQ,UAAU,SAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;AAAA,IAC9D,UAAU,CAAC,OAAO;AAAA,IAClB,QAAQ,QAAQ,eAAe,OAC3B,EAAE,SAAS,iBAAiB,UAAU,cAAc,SAAS,KAAK,IAClE,QAAQ,kBAAkB,OACxB,EAAE,SAAS,iBAAiB,UAAU,eAAe,SAAS,OAAO,WAAW,CAAC,iBAAiB,EAAE,IACpG,QAAQ,YAAY,QACpB,EAAE,SAAS,OAAO,UAAU,CAAC,eAAe,SAAS,EAAE,IACvD;AAAA,MACE,SAAS;AAAA,MACT,UAAU,QAAQ,YAAY;AAAA,MAC9B,SAAS;AAAA,MACT,GAAI,QAAQ,aAAa,SAAY,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,MACvE,WAAW,CAAC,iBAAiB;AAAA,IAC/B;AAAA,EACR,CAAC;AACH;AAEO,IAAM,yBAAwC;AAAA;AAAA,EAEnD,OAAO,wBAAwB,0CAA0C,CAAC,CAAC,cAAc,wBAAwB,GAAG,CAAC,mBAAmB,qBAAqB,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,cAAc,YAAY,CAAC,GAAG,2DAA2D,EAAE,UAAU,SAAS,CAAC;AAAA,EACnS,OAAO,0BAA0B,yBAAyB,CAAC,CAAC,cAAc,wBAAwB,GAAG,CAAC,mBAAmB,eAAe,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,cAAc,YAAY,GAAG,CAAC,gBAAgB,kBAAkB,GAAG,CAAC,kBAAkB,KAAK,CAAC,GAAG,oCAAoC,EAAE,UAAU,UAAU,QAAQ,QAAQ,MAAM,iBAAiB,CAAC;AAAA,EAChW,OAAO,mBAAmB,uBAAuB,CAAC,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,yBAAyB,GAAG,CAAC,cAAc,YAAY,GAAG,CAAC,cAAc,qBAAqB,CAAC,GAAG,gFAAgF,EAAE,UAAU,SAAS,CAAC;AAAA,EACjS,OAAO,sBAAsB,WAAW,CAAC,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,eAAe,GAAG,CAAC,cAAc,6BAA6B,CAAC,GAAG,0DAA0D,EAAE,UAAU,SAAS,CAAC;AAAA,EAClO,OAAO,sBAAsB,yBAAyB,CAAC,CAAC,cAAc,sBAAsB,GAAG,CAAC,mBAAmB,UAAU,CAAC,GAAG,yDAAyD,EAAE,UAAU,SAAS,CAAC;AAAA,EAChN,OAAO,qBAAqB,iCAAiC,CAAC,CAAC,mBAAmB,UAAU,GAAG,CAAC,cAAc,oBAAoB,GAAG,CAAC,cAAc,OAAO,CAAC,GAAG,uEAAuE,EAAE,UAAU,SAAS,CAAC;AAAA,EAC5P,OAAO,sBAAsB,UAAU,CAAC,CAAC,cAAc,qCAAqC,GAAG,CAAC,UAAU,iEAAiE,GAAG,CAAC,mBAAmB,IAAI,GAAG,CAAC,mBAAmB,eAAe,CAAC,GAAG,kFAAkF,EAAE,UAAU,SAAS,CAAC;AAAA,EACxV,OAAO,qBAAqB,qCAAqC,CAAC,CAAC,cAAc,wBAAwB,GAAG,CAAC,mBAAmB,eAAe,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,GAAG,CAAC,GAAG,wDAAwD,EAAE,UAAU,SAAS,CAAC;AAAA,EAC9Q,OAAO,sBAAsB,kBAAkB,CAAC,CAAC,cAAc,wBAAwB,GAAG,CAAC,mBAAmB,eAAe,GAAG,CAAC,UAAU,iEAAiE,GAAG,CAAC,cAAc,YAAY,CAAC,GAAG,kEAAkE,EAAE,UAAU,SAAS,CAAC;AAAA;AAAA,EAGtU,OAAO,oBAAoB,yBAAyB,CAAC,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,GAAG,GAAG,CAAC,kBAAkB,MAAM,GAAG,CAAC,cAAc,MAAM,GAAG,CAAC,mBAAmB,eAAe,CAAC,GAAG,gGAAgG,EAAE,SAAS,OAAO,OAAO,mKAAmK,CAAC;AAAA,EAC1d,OAAO,qBAAqB,iBAAiB,CAAC,CAAC,UAAU,KAAK,GAAG,CAAC,cAAc,YAAY,GAAG,CAAC,mBAAmB,iBAAiB,GAAG,CAAC,cAAc,OAAO,CAAC,GAAG,6DAA6D,EAAE,UAAU,OAAO,CAAC;AAAA,EAClP,OAAO,kBAAkB,iBAAiB,CAAC,CAAC,UAAU,mCAAmC,GAAG,CAAC,cAAc,aAAa,GAAG,CAAC,mBAAmB,6BAA6B,CAAC,GAAG,+DAA+D,EAAE,UAAU,OAAO,CAAC;AAAA,EACnQ,OAAO,WAAW,OAAO,CAAC,CAAC,cAAc,2CAA2C,GAAG,CAAC,mBAAmB,mBAAmB,CAAC,GAAG,8CAA8C,EAAE,UAAU,OAAO,CAAC;AAAA,EACpM,OAAO,kBAAkB,cAAc,CAAC,CAAC,cAAc,kBAAkB,GAAG,CAAC,mBAAmB,eAAe,CAAC,GAAG,2DAA2D,EAAE,UAAU,OAAO,CAAC;AAAA,EAClM,OAAO,uBAAuB,+CAA+C,CAAC,CAAC,cAAc,+GAA+G,GAAG,CAAC,UAAU,uFAAuF,GAAG,CAAC,mBAAmB,mBAAmB,GAAG,CAAC,mBAAmB,OAAO,CAAC,GAAG,mEAAmE,EAAE,UAAU,gBAAgB,YAAY,MAAM,UAAU,kBAAkB,CAAC;AAAA,EAC3gB,OAAO,kBAAkB,gBAAgB,CAAC,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,UAAU,GAAG,CAAC,cAAc,YAAY,GAAG,CAAC,mBAAmB,GAAG,CAAC,GAAG,2CAA2C,EAAE,UAAU,OAAO,CAAC;AAAA,EACtN,OAAO,iBAAiB,eAAe,CAAC,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,mBAAmB,GAAG,CAAC,cAAc,WAAW,GAAG,CAAC,cAAc,YAAY,CAAC,GAAG,0CAA0C,EAAE,UAAU,OAAO,CAAC;AAAA;AAAA,EAG/N,OAAO,sBAAsB,8BAA8B,CAAC,CAAC,cAAc,eAAe,GAAG,CAAC,cAAc,YAAY,GAAG,CAAC,mBAAmB,MAAM,CAAC,GAAG,oFAAoF,EAAE,UAAU,OAAO,CAAC;AAAA,EACjQ,OAAO,gBAAgB,wBAAwB,CAAC,CAAC,UAAU,kBAAkB,GAAG,CAAC,cAAc,eAAe,GAAG,CAAC,cAAc,YAAY,GAAG,CAAC,mBAAmB,MAAM,CAAC,GAAG,oEAAoE,EAAE,UAAU,OAAO,CAAC;AAAA,EACrQ,OAAO,sBAAsB,sBAAsB,CAAC,CAAC,cAAc,yBAAyB,GAAG,CAAC,cAAc,yBAAyB,GAAG,CAAC,WAAW,KAAK,GAAG,CAAC,kBAAkB,0CAA0C,CAAC,GAAG,kFAAkF,EAAE,UAAU,OAAO,CAAC;AAAA,EACrU,OAAO,0BAA0B,uBAAuB,CAAC,CAAC,cAAc,uCAAuC,GAAG,CAAC,mBAAmB,uBAAuB,GAAG,CAAC,cAAc,YAAY,CAAC,GAAG,sEAAsE,EAAE,UAAU,OAAO,CAAC;AAAA,EACzR,OAAO,wBAAwB,uCAAuC,CAAC,CAAC,cAAc,oBAAoB,GAAG,CAAC,UAAU,kBAAkB,GAAG,CAAC,mBAAmB,MAAM,CAAC,GAAG,qDAAqD,EAAE,SAAS,MAAM,CAAC;AAAA,EAClP,OAAO,YAAY,iBAAiB,CAAC,CAAC,UAAU,KAAK,GAAG,CAAC,kBAAkB,OAAO,GAAG,CAAC,cAAc,aAAa,GAAG,CAAC,mBAAmB,uBAAuB,CAAC,GAAG,6EAA6E,EAAE,UAAU,QAAQ,OAAO,gFAAgF,CAAC;AAAA,EAC5V,OAAO,aAAa,kCAAkC,CAAC,CAAC,cAAc,qBAAqB,GAAG,CAAC,UAAU,iEAAiE,GAAG,CAAC,mBAAmB,MAAM,CAAC,GAAG,kDAAkD,EAAE,UAAU,QAAQ,OAAO,gKAAgK,CAAC;AAAA;AAAA,EAGzb,OAAO,kBAAkB,iBAAiB,CAAC,CAAC,cAAc,oBAAoB,GAAG,CAAC,mBAAmB,MAAM,CAAC,GAAG,qFAAgF,EAAE,UAAU,KAAK,CAAC;AAAA,EACjN,OAAO,gBAAgB,iBAAiB,CAAC,CAAC,cAAc,qDAAqD,GAAG,CAAC,mBAAmB,MAAM,CAAC,GAAG,8BAA8B,EAAE,UAAU,KAAK,CAAC;AAAA,EAC9L,OAAO,oBAAoB,kBAAkB,CAAC,CAAC,UAAU,KAAK,GAAG,CAAC,cAAc,iBAAiB,GAAG,CAAC,mBAAmB,yBAAyB,CAAC,GAAG,oDAAoD,EAAE,UAAU,OAAO,CAAC;AAAA,EAC7N,OAAO,iBAAiB,eAAe,CAAC,CAAC,cAAc,YAAY,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,MAAM,CAAC,GAAG,8CAA8C,EAAE,UAAU,OAAO,CAAC;AAAA,EACzL,OAAO,gBAAgB,qBAAqB,CAAC,CAAC,cAAc,aAAa,GAAG,CAAC,UAAU,KAAK,CAAC,GAAG,mFAAmF,EAAE,UAAU,OAAO,CAAC;AAAA,EACvM,OAAO,uBAAuB,qBAAqB,CAAC,CAAC,cAAc,aAAa,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,yBAAyB,CAAC,GAAG,oEAAoE,EAAE,UAAU,OAAO,CAAC;AAAA,EAC/O,OAAO,YAAY,QAAQ,CAAC,CAAC,cAAc,aAAa,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,UAAU,GAAG,CAAC,cAAc,YAAY,CAAC,GAAG,sCAAsC,EAAE,UAAU,OAAO,CAAC;AAAA,EACxM,OAAO,aAAa,SAAS,CAAC,CAAC,cAAc,YAAY,GAAG,CAAC,mBAAmB,+BAA+B,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,cAAc,YAAY,CAAC,GAAG,kEAAkE,EAAE,UAAU,OAAO,CAAC;AAAA,EAC1P,OAAO,aAAa,0BAA0B,CAAC,CAAC,cAAc,cAAc,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,eAAe,GAAG,CAAC,cAAc,OAAO,GAAG,CAAC,SAAS,iBAAiB,CAAC,GAAG,yDAAyD,EAAE,UAAU,YAAY,MAAM,2BAA2B,CAAC;AAAA;AAAA,EAGnT,OAAO,cAAc,UAAU,CAAC,CAAC,cAAc,wCAAwC,GAAG,CAAC,UAAU,KAAK,CAAC,GAAG,sDAAsD,EAAE,UAAU,MAAM,CAAC;AAAA,EACvL,OAAO,6BAA6B,2BAA2B,CAAC,CAAC,cAAc,WAAW,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,cAAc,OAAO,CAAC,GAAG,iEAAiE,EAAE,UAAU,MAAM,CAAC;AAAA,EAC9N,OAAO,0BAA0B,wBAAwB,CAAC,CAAC,cAAc,uCAAuC,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,2CAA2C,GAAG,CAAC,gBAAgB,mCAAmC,CAAC,GAAG,gFAAgF,EAAE,UAAU,OAAO,QAAQ,QAAQ,MAAM,eAAe,OAAO,8FAA8F,CAAC;AAAA,EAC9e,OAAO,mBAAmB,wBAAwB,CAAC,CAAC,cAAc,wCAAwC,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,MAAM,CAAC,GAAG,gCAAgC,EAAE,UAAU,MAAM,CAAC;AAAA,EACjN,OAAO,oBAAoB,qBAAqB,CAAC,CAAC,cAAc,iBAAiB,GAAG,CAAC,mBAAmB,yCAAyC,GAAG,CAAC,UAAU,KAAK,CAAC,GAAG,2DAA2D,EAAE,UAAU,OAAO,CAAC;AAAA,EACvP,OAAO,oBAAoB,oBAAoB,CAAC,CAAC,mBAAmB,yCAAyC,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,cAAc,MAAM,GAAG,CAAC,cAAc,OAAO,CAAC,GAAG,6DAA6D,EAAE,eAAe,MAAM,OAAO,kPAAwO,CAAC;AAAA,EACzf,OAAO,uBAAuB,gCAAgC,CAAC,CAAC,cAAc,4DAA4D,GAAG,CAAC,UAAU,gDAAgD,GAAG,CAAC,mBAAmB,MAAM,CAAC,GAAG,6FAA6F,EAAE,UAAU,cAAc,UAAU,YAAY,UAAU,gBAAgB,OAAO,0OAA0O,CAAC;AAAA,EACloB,OAAO,yBAAyB,mBAAmB,CAAC,CAAC,cAAc,0DAA0D,GAAG,CAAC,mBAAmB,mBAAmB,GAAG,CAAC,cAAc,YAAY,CAAC,GAAG,0DAA0D,EAAE,UAAU,SAAS,CAAC;AAAA,EACzR,OAAO,iBAAiB,aAAa,CAAC,CAAC,cAAc,mBAAmB,GAAG,CAAC,UAAU,sFAAsF,GAAG,CAAC,mBAAmB,eAAe,CAAC,GAAG,oEAAoE,EAAE,UAAU,SAAS,CAAC;AAAA,EAChT,OAAO,yBAAyB,kCAAkC,CAAC,CAAC,cAAc,qFAAqF,GAAG,CAAC,mBAAmB,mBAAmB,CAAC,GAAG,kEAAkE,EAAE,UAAU,SAAS,OAAO,mJAAmJ,CAAC;AAAA,EACvc,OAAO,mBAAmB,cAAc,CAAC,CAAC,cAAc,kBAAkB,GAAG,CAAC,MAAM,oBAAoB,CAAC,GAAG,2DAA2D,EAAE,UAAU,OAAO,CAAC;AAAA;AAAA,EAG3L,OAAO,cAAc,UAAU,CAAC,CAAC,cAAc,cAAc,GAAG,CAAC,mBAAmB,eAAe,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,cAAc,YAAY,CAAC,GAAG,gEAAgE,EAAE,UAAU,YAAY,OAAO,yFAAyF,CAAC;AAAA,EACjV,OAAO,eAAe,WAAW,CAAC,CAAC,cAAc,uBAAuB,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,iBAAiB,UAAU,GAAG,CAAC,iBAAiB,sCAAsC,GAAG,CAAC,mBAAmB,mBAAmB,GAAG,CAAC,cAAc,YAAY,CAAC,GAAG,sDAAsD,EAAE,UAAU,WAAW,CAAC;AAAA,EAC/U,OAAO,gBAAgB,YAAY,CAAC,CAAC,cAAc,iBAAiB,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,mBAAmB,CAAC,GAAG,yCAAyC,EAAE,UAAU,WAAW,CAAC;AAAA,EACtM,OAAO,UAAU,MAAM,CAAC,CAAC,cAAc,WAAW,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,yBAAyB,GAAG,CAAC,cAAc,YAAY,CAAC,GAAG,qCAAqC,EAAE,SAAS,MAAM,CAAC;AAAA,EAC9M,OAAO,oBAAoB,kBAAkB,CAAC,CAAC,cAAc,4BAA4B,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,MAAM,CAAC,GAAG,oGAAoG,EAAE,UAAU,MAAM,UAAU,kBAAkB,OAAO,iXAAiX,CAAC;AAAA,EACxpB,OAAO,cAAc,sBAAsB,CAAC,CAAC,cAAc,oBAAoB,GAAG,CAAC,mBAAmB,MAAM,GAAG,CAAC,mBAAmB,GAAG,CAAC,GAAG,8DAA8D,EAAE,UAAU,MAAM,UAAU,iBAAiB,CAAC;AAAA,EACtP,OAAO,oBAAoB,eAAe,CAAC,CAAC,cAAc,iBAAiB,GAAG,CAAC,UAAU,KAAK,CAAC,GAAG,2CAA2C,EAAE,eAAe,MAAM,UAAU,iBAAiB,CAAC;AAClM;;;ACpGA,SAAS,QAAQ,IAAY,OAAe,SAAiC,YAAoB,UAAmB,OAA6B;AAC/I,SAAO,IAAI;AAAA,IACT;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;AAAA,IACvC,UAAU,CAAC,EAAE,SAAS,UAAU,SAAS,aAAa,MAAM,CAAC;AAAA,IAC7D,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,UAAU;AAAA,MACV,SAAS;AAAA,MACT,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,MAC7C,WAAW,CAAC,iBAAiB;AAAA,IAC/B;AAAA,EACF,CAAC;AACH;AAEO,IAAM,gBAA+B;AAAA;AAAA;AAAA;AAAA,EAI1C;AAAA,IACE;AAAA,IACA;AAAA,IACA,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,wBAAwB,GAAG,CAAC,mBAAmB,eAAe,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,cAAc,YAAY,CAAC;AAAA,IAC1J;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE;AAAA,IACA;AAAA,IACA,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,mBAAmB,GAAG,CAAC,cAAc,YAAY,GAAG,CAAC,cAAc,qBAAqB,CAAC;AAAA,IAC3J;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE;AAAA,IACA;AAAA,IACA,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,eAAe,GAAG,CAAC,cAAc,4BAA4B,CAAC;AAAA,IAChI;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE;AAAA,IACA;AAAA,IACA,CAAC,CAAC,mBAAmB,UAAU,GAAG,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,oBAAoB,GAAG,CAAC,cAAc,OAAO,CAAC;AAAA,IACzH;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE;AAAA,IACA;AAAA,IACA,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,qCAAqC,GAAG,CAAC,UAAU,iEAAiE,GAAG,CAAC,mBAAmB,IAAI,GAAG,CAAC,mBAAmB,eAAe,CAAC;AAAA,IAChO;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE;AAAA,IACA;AAAA,IACA,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,aAAa,GAAG,CAAC,UAAU,KAAK,CAAC;AAAA,IAC3E;AAAA,IACA;AAAA,EACF;AAAA,EACA,QAAQ,QAAQ,QAAQ,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,aAAa,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,UAAU,GAAG,CAAC,cAAc,YAAY,CAAC,GAAG,6BAA6B,MAAM;AAAA,EACxM;AAAA,IACE;AAAA,IACA;AAAA,IACA,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,oBAAoB,GAAG,CAAC,mBAAmB,MAAM,CAAC;AAAA,IAC5F;AAAA,IACA;AAAA,EACF;AAAA,EACA,QAAQ,cAAc,cAAc,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,cAAc,wDAAwD,GAAG,CAAC,mBAAmB,cAAc,GAAG,CAAC,cAAc,OAAO,CAAC,GAAG,qFAAqF,MAAM;AAAA,EACtT,QAAQ,SAAS,SAAS,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,UAAU,mCAAmC,GAAG,CAAC,cAAc,aAAa,GAAG,CAAC,mBAAmB,6BAA6B,CAAC,GAAG,+DAA+D,MAAM;AAAA,EAC/P,QAAQ,UAAU,UAAU,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,eAAe,GAAG,CAAC,cAAc,YAAY,GAAG,CAAC,mBAAmB,MAAM,CAAC,GAAG,4CAA4C,MAAM;AAAA,EACtM,QAAQ,mBAAmB,mBAAmB,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,yBAAyB,GAAG,CAAC,cAAc,yBAAyB,CAAC,GAAG,6BAA6B,MAAM;AAAA,EACnM,QAAQ,qBAAqB,qBAAqB,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,uCAAuC,GAAG,CAAC,mBAAmB,uBAAuB,GAAG,CAAC,cAAc,YAAY,CAAC,GAAG,8BAA8B,MAAM;AAAA,EACvP,QAAQ,UAAU,UAAU,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,yCAAyC,GAAG,CAAC,UAAU,KAAK,CAAC,GAAG,+DAA+D,KAAK;AAAA,EAC1M,QAAQ,aAAa,aAAa,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,uCAAuC,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,2CAA2C,CAAC,GAAG,kEAAkE,OAAO,6FAA6F;AAAA,EAClX,QAAQ,gBAAgB,gBAAgB,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,iBAAiB,GAAG,CAAC,mBAAmB,yCAAyC,GAAG,CAAC,UAAU,KAAK,CAAC,GAAG,8BAA8B,MAAM;AAAA,EAC9N,QAAQ,qBAAqB,mBAAmB,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,0DAA0D,GAAG,CAAC,mBAAmB,mBAAmB,CAAC,GAAG,mFAAmF,QAAQ;AAAA,EAC7R,QAAQ,WAAW,WAAW,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,uBAAuB,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,mBAAmB,mBAAmB,GAAG,CAAC,cAAc,YAAY,CAAC,GAAG,mFAA8E,YAAY,+FAA+F;AAAA,EACvX,QAAQ,UAAU,UAAU,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,cAAc,GAAG,CAAC,mBAAmB,eAAe,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,cAAc,YAAY,CAAC,GAAG,yCAAyC,UAAU;AAAA,EAClO,QAAQ,eAAe,OAAO,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,kBAAkB,GAAG,CAAC,MAAM,oBAAoB,CAAC,GAAG,2DAA2D,MAAM;AAAA,EAE7L,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,CAAC,MAAM,uBAAuB,CAAC;AAAA,IACzC,QAAQ,EAAE,SAAS,iBAAiB,UAAU,eAAe,SAAS,OAAO,UAAU,GAAG;AAAA,EAC5F,CAAC;AAAA,EAED,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,CAAC,EAAE,SAAS,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,UAAU,KAAK,CAAC,GAAG,UAAU,SAAS,aAAa,MAAM,CAAC;AAAA,IAC5G,QAAQ,EAAE,SAAS,OAAO,UAAU,GAAG;AAAA,EACzC,CAAC;AAAA;AAAA;AAAA;AAAA,EAKD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,MAAM,+GAA+G,CAAC;AAAA,IACjI,QAAQ,EAAE,SAAS,iBAAiB,UAAU,cAAc,SAAS,KAAK;AAAA,EAC5E,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU;AAAA,MACR;AAAA,QACE,SAAS;AAAA,UACP,CAAC,QAAQ,cAAc;AAAA,UACvB,CAAC,cAAc,YAAY;AAAA,UAC3B,CAAC,aAAa,oEAAoE;AAAA,UAClF,CAAC,oBAAoB,IAAI;AAAA,UACzB,CAAC,sBAAsB,SAAS;AAAA,UAChC,CAAC,cAAc,uGAAuG;AAAA,UACtH,CAAC,UAAU,iEAAiE;AAAA,UAC5E,CAAC,mBAAmB,mBAAmB;AAAA,UACvC,CAAC,mBAAmB,OAAO;AAAA,QAC7B;AAAA,QACA,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,SAAS,iBAAiB,UAAU,cAAc,SAAS,MAAM,WAAW,CAAC,cAAc,EAAE;AAAA,EACzG,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,MAAM,iIAAiI,CAAC;AAAA,IACnJ,QAAQ,EAAE,SAAS,iBAAiB,UAAU,cAAc,SAAS,KAAK;AAAA,EAC5E,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,MAAM,wGAAwG,CAAC;AAAA,IAC1H,QAAQ,EAAE,SAAS,iBAAiB,UAAU,cAAc,SAAS,KAAK;AAAA,EAC5E,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,MAAM,+FAA+F,CAAC;AAAA,IACjH,QAAQ,EAAE,SAAS,iBAAiB,UAAU,cAAc,SAAS,KAAK;AAAA,EAC5E,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,MAAM,2CAA2C,GAAG,MAAM,uCAAuC,CAAC;AAAA,IAClH,QAAQ,EAAE,SAAS,iBAAiB,UAAU,WAAW,SAAS,MAAM,UAAU,SAAS;AAAA,EAC7F,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,MAAM,8DAA8D,GAAG,MAAM,aAAa,CAAC;AAAA,IAC3G,QAAQ,EAAE,SAAS,iBAAiB,UAAU,WAAW,SAAS,MAAM,UAAU,QAAQ;AAAA,EAC5F,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,MAAM,mEAAmE,GAAG,MAAM,eAAe,CAAC;AAAA,IAClH,QAAQ,EAAE,SAAS,iBAAiB,UAAU,WAAW,SAAS,MAAM,UAAU,SAAS;AAAA,EAC7F,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,GAAG,MAAM,cAAc,GAAG,MAAM,UAAU,CAAC;AAAA,IACxD,QAAQ,EAAE,SAAS,iBAAiB,UAAU,WAAW,SAAS,KAAK;AAAA,EACzE,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,MAAM,kIAAkI,CAAC;AAAA,IACpJ,QAAQ,EAAE,SAAS,iBAAiB,UAAU,WAAW,SAAS,KAAK;AAAA,EACzE,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,SAAS,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,cAAc,4DAA4D,GAAG,CAAC,UAAU,KAAK,CAAC,GAAG,UAAU,QAAQ,aAAa,MAAM,CAAC;AAAA,IACzL,QAAQ,EAAE,SAAS,iBAAiB,UAAU,WAAW,SAAS,KAAK;AAAA,EACzE,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,CAAC,MAAM,wEAAwE,CAAC;AAAA,IAC1F,QAAQ,EAAE,SAAS,iBAAiB,UAAU,WAAW,SAAS,KAAK;AAAA,EACzE,CAAC;AAAA,EACD,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,CAAC,MAAM,uFAAuF,CAAC;AAAA,IACzG,QAAQ,EAAE,SAAS,iBAAiB,SAAS,KAAK;AAAA,EACpD,CAAC;AACH;;;AC9PA,SAAS,IAAI,IAAY,OAAe,WAAmB,UAAkB,YAAoB,OAA6B;AAC5H,SAAO,IAAI;AAAA,IACT;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;AAAA,IACvC,UAAU,CAAC,MAAM,SAAS,CAAC;AAAA,IAC3B,QAAQ,EAAE,SAAS,iBAAiB,SAAS,MAAM,UAAU,WAAW,CAAC,iBAAiB,EAAE;AAAA,EAC9F,CAAC;AACH;AAEO,IAAM,qBAAoC;AAAA,EAC/C,IAAI,aAAa,aAAa,sEAAsE,aAAa,uHAAuH;AAAA,EACxO,IAAI,qBAAqB,mBAAmB,sFAAsF,aAAa,yDAAoD;AAAA,EACnM,IAAI,cAAc,cAAc,+EAA+E,cAAc,8BAA8B;AAAA,EAC3J,IAAI,WAAW,WAAW,iEAAiE,WAAW,iHAA4G;AAAA,EAClN,IAAI,UAAU,UAAU,4FAA4F,UAAU,+CAA+C;AAAA,EAC7K,IAAI,WAAW,WAAW,uEAAuE,WAAW,gDAAgD;AAAA,EAC5J,IAAI,cAAc,iBAAiB,uFAAuF,cAAc,qCAAqC;AAAA,EAC7K,IAAI,eAAe,eAAe,qGAAqG,eAAe,gDAAgD;AAAA,EACtM,IAAI,cAAc,cAAc,0EAA0E,cAAc,gDAAgD;AAAA,EACxK,IAAI,eAAe,eAAe,iDAAiD,eAAe,mHAA8G;AAAA,EAEhN,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,MAAM,KAAK,EAAE,QAAQ,GAAG,GAAG,CAAC,GAAG,WAAW;AAAA,MAClD,GAAG,MAAM,6EAA6E;AAAA,MACtF,MAAM,aAAa,QAAQ,CAAC;AAAA,MAC5B,MAAM,QAAQ;AAAA,IAChB,EAAE;AAAA,IACF,QAAQ,EAAE,SAAS,iBAAiB,SAAS,MAAM,UAAU,aAAa;AAAA,EAC5E,CAAC;AAAA,EAED,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OACE;AAAA,IACF,UAAU,CAAC,MAAM,wFAAwF,CAAC;AAAA,IAC1G,QAAQ,EAAE,SAAS,iBAAiB,UAAU,gBAAgB,SAAS,KAAK;AAAA,EAC9E,CAAC;AAAA,EAED,IAAI;AAAA,IACF,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU,CAAC,MAAM,2CAA2C,CAAC;AAAA,IAC7D,QAAQ,EAAE,SAAS,iBAAiB,SAAS,OAAO,UAAU,gBAAgB,UAAU,GAAG;AAAA,EAC7F,CAAC;AACH;;;AC9EA;AACA;AAoHA,IAAM,gBAAgB,KAAK,IAAI,MAAM,GAAG,IAAI,GAAG,GAAG,CAAC;AAU5C,SAAS,WAAW,IAAoB;AAC7C,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,GAAG,QAAQ,KAAK;AAClC,YAAQ,GAAG,WAAW,CAAC;AACvB,WAAO,KAAK,KAAK,MAAM,QAAU;AAAA,EACnC;AACA,QAAM,QAAQ,SAAS;AACvB,SAAO,UAAW,SAAS,IAAK,GAAI,IAAK,QAAQ,OAAS,CAAC;AAC7D;AAGA,SAAS,aAAa,SAAqD;AACzE,QAAM,WAAW,MAAa;AAC5B,UAAM,OAAO,OAAO,IAAI,MAAM,WAAW,GAAG,EAAE,MAAM,YAAY,CAAC;AAAA,EACnE;AACA,QAAM,cAAc,MAAa;AAC/B,UAAM,OAAO,OAAO,IAAI,MAAM,WAAW,GAAG,EAAE,MAAM,WAAW,CAAC;AAAA,EAClE;AACA,SAAO;AAAA,IACL,MAAM,QAAQ,IAAI;AAChB,YAAM,MAAM,QAAQ,GAAG;AACvB,UAAI,QAAQ,OAAW,QAAO,SAAS;AACvC,UAAI,IAAI,gBAAgB,KAAM,QAAO,YAAY;AACjD,YAAM,QAAQ,IAAI,UAAU,EAAE;AAC9B,aAAO,UAAU,SAAY,SAAS,IAAI,CAAC,GAAG,KAAK;AAAA,IACrD;AAAA,IACA,MAAM,iBAAiB,UAAU;AAC/B,YAAM,MAAM,QAAQ,GAAG;AACvB,UAAI,QAAQ,OAAW,QAAO,SAAS;AACvC,UAAI,IAAI,gBAAgB,KAAM,QAAO,YAAY;AACjD,YAAM,YAAY,IAAI,UAAU,QAAQ;AACxC,aAAO,cAAc,SAAY,SAAS,IAAI,CAAC,GAAG,SAAS;AAAA,IAC7D;AAAA,EACF;AACF;AAEA,SAAS,QAAQ,SAAsB,YAAoB,WAAiC;AAC1F,QAAM,UAA6C,CAAC;AACpD,QAAM,QAAkB,CAAC;AACzB,aAAW,CAAC,MAAM,KAAK,KAAK,QAAQ,SAAS;AAC3C,UAAM,MAAM,KAAK,YAAY;AAC7B,UAAM,WAAW,QAAQ,GAAG;AAC5B,YAAQ,GAAG,IAAI,aAAa,SAAY,QAAQ,MAAM,QAAQ,QAAQ,IAAI,CAAC,GAAG,UAAU,KAAK,IAAI,CAAC,UAAU,KAAK;AACjH,UAAM,KAAK,IAAI;AAAA,EACjB;AAEA,SAAO,YAAY;AAAA,IACjB,QAAQ,QAAQ,UAAU;AAAA,IAC1B,KAAK,QAAQ,QAAQ;AAAA,IACrB;AAAA,IACA,YAAY;AAAA,IACZ,IAAI,QAAQ,MAAM;AAAA,IAClB;AAAA,IACA,UAAU,QAAQ,YAAY;AAAA,IAC9B,aAAa,QAAQ,eAAe;AAAA,IACpC,GAAI,QAAQ,mBAAmB,SAAY,EAAE,gBAAgB,QAAQ,eAAe,IAAI,CAAC;AAAA,IACzF,GAAI,QAAQ,mBAAmB,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAC;AAAA,EACpE,CAAC;AACH;AAEA,SAAS,QAAW,OAA+D;AACjF,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,MAAM,QAAQ,KAAK,IAAK,QAAyB,CAAC,KAAU;AACrE;AAEA,SAAS,kBAAkB,MAAmB,QAAuB,eAAkC;AACrG,QAAM,EAAE,YAAY,UAAU,QAAQ,IAAI;AAC1C,QAAM,SAAsB,KAAK;AACjC,QAAM,WAAqB,CAAC;AAE5B,QAAM,WAAW,QAAQ,OAAO,OAAO;AACvC,MAAI,YAAY,CAAC,SAAS,SAAS,WAAW,OAAO,GAAG;AACtD,aAAS,KAAK,gBAAgB,WAAW,OAAO,eAAe,SAAS,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,MAAM,CAAC,EAAE;AAAA,EAC7G;AAEA,QAAM,UAAU,QAAQ,OAAO,QAAQ;AACvC,MAAI,WAAW,CAAC,QAAQ,SAAS,WAAW,QAAQ,GAAG;AACrD,aAAS,KAAK,iBAAiB,WAAW,QAAQ,eAAe,QAAQ,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,MAAM,CAAC,EAAE;AAAA,EAC9G;AAEA,MAAI,OAAO,YAAY,UAAa,WAAW,YAAY,OAAO,SAAS;AACzE,aAAS,KAAK,eAAe,WAAW,OAAO,cAAc,OAAO,OAAO,EAAE;AAAA,EAC/E;AAEA,MAAI,OAAO,aAAa,QAAW;AACjC,UAAM,aAAa,IAAI,IAAI,CAAC,WAAW,UAAU,GAAG,WAAW,SAAS,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,OAAO,CAAC;AAC/G,QAAI,CAAC,WAAW,IAAI,OAAO,QAAQ,GAAG;AACpC,eAAS,KAAK,aAAa,OAAO,QAAQ,0BAA0B,CAAC,GAAG,UAAU,EAAE,KAAK,IAAI,KAAK,MAAM,GAAG;AAAA,IAC7G;AAAA,EACF;AAEA,MAAI,OAAO,aAAa,UAAa,WAAW,QAAQ,OAAO,UAAU;AACvE,aAAS,KAAK,aAAa,WAAW,KAAK,uBAAuB,OAAO,QAAQ,EAAE;AAAA,EACrF;AACA,MAAI,OAAO,aAAa,UAAa,WAAW,QAAQ,OAAO,UAAU;AACvE,aAAS,KAAK,aAAa,WAAW,KAAK,sBAAsB,OAAO,QAAQ,EAAE;AAAA,EACpF;AAEA,QAAM,QAAQ,IAAI,IAAI,CAAC,GAAG,WAAW,UAAU,GAAG,WAAW,aAAa,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC;AAClG,aAAW,YAAY,OAAO,aAAa,CAAC,GAAG;AAC7C,QAAI,CAAC,MAAM,IAAI,QAAQ,EAAG,UAAS,KAAK,sBAAsB,QAAQ,iCAAiC,CAAC,GAAG,KAAK,EAAE,KAAK,IAAI,KAAK,MAAM,GAAG;AAAA,EAC3I;AACA,aAAW,YAAY,OAAO,gBAAgB,CAAC,GAAG;AAChD,QAAI,MAAM,IAAI,QAAQ,GAAG;AACvB,YAAM,UAAU,CAAC,GAAG,WAAW,UAAU,GAAG,WAAW,aAAa,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ,GAAG,WAAW;AACvH,eAAS,KAAK,aAAa,QAAQ,gCAAgC,OAAO,EAAE;AAAA,IAC9E;AAAA,EACF;AAEA,QAAM,UAAU,gBAAgB,QAAQ,OAAO,MAAM,IAAI;AACzD,MAAI,WAAW,CAAC,QAAQ,SAAS,SAAS,MAAM,GAAG;AACjD,aAAS,KAAK,eAAe,SAAS,MAAM,YAAY,SAAS,IAAI,gBAAgB,QAAQ,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,MAAM,CAAC,EAAE;AAAA,EAClI;AACA,aAAW,UAAU,OAAO,eAAe,CAAC,GAAG;AAC7C,QAAI,SAAS,WAAW,OAAQ,UAAS,KAAK,WAAW,MAAM,uCAAuC,SAAS,IAAI,IAAI;AAAA,EACzH;AAEA,MAAI,OAAO,YAAY,UAAa,QAAQ,SAAS,OAAO,SAAS;AACnE,aAAS,KAAK,gBAAgB,QAAQ,IAAI,gBAAgB,OAAO,OAAO,oBAAoB,SAAS,MAAM,IAAI;AAAA,EACjH;AAEA,SAAO;AACT;AAEA,eAAsB,QAAQ,SAAqB,OAAoB,MAAmB,WAAmB,UAA+B,gBAAgB,MAA2B;AACrL,QAAM,WAAW,KAAK,YAAY,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,SAAS,IAAI,UAAU,CAAC;AACtF,MAAI,QAAQ,SAAS,GAAG;AACtB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU,CAAC;AAAA,MACX,OAAO;AAAA,MACP,UAAU,CAAC;AAAA,MACX,eAAe;AAAA,MACf,SAAS,sDAAsD,QAAQ,KAAK,IAAI,CAAC;AAAA,MACjF,YAAY;AAAA,IACd;AAAA,EACF;AAEA,QAAM,aAAa,WAAW,KAAK,EAAE;AACrC,QAAM,WAA4B,CAAC;AACnC,QAAM,QAAQ,YAAY,IAAI;AAK9B,MAAI;AACJ,MAAI,KAAK,cAAc,QAAW;AAChC,UAAM,IAAI,SAAS;AACnB,UAAM,OAAO,QAAQ,KAAK,SAAS,CAAC,GAAI,YAAY,MAAM,IAAI,CAAC;AAC/D,sBAAkB,QAAQ,eAAe,MAAM,KAAK,SAAS,GAAG,MAAM,GAAG,EAAE,CAAC;AAAA,EAC9E;AAEA,aAAW,WAAW,KAAK,UAAU;AACnC,UAAM,IAAI,aAAa,QAAQ,QAAQ,EAAE;AACzC,UAAM,gBACJ,oBAAoB,SAAY,UAAU,EAAE,GAAG,SAAS,SAAS,CAAC,GAAG,QAAQ,SAAS,CAAC,UAAU,eAAe,CAAC,EAAE;AACrH,UAAM,QAAQ,QAAQ,eAAe,YAAY,MAAM,IAAI,CAAC;AAC5D,UAAM,EAAE,YAAY,UAAU,QAAQ,IAAI,MAAM,QAAQ,OAAO,KAAK;AACpE,aAAS,KAAK,EAAE,YAAY,UAAU,QAAQ,CAAC;AAAA,EACjD;AAEA,QAAM,QAAQ,SAAS,SAAS,SAAS,CAAC;AAC1C,QAAM,WAAW,kBAAkB,MAAM,OAAO,aAAa;AAI7D,MAAI,gBAAgB;AAGpB,MAAI,KAAK,aAAa,WAAW,KAAK,iBAAiB,QAAW;AAChE,eAAW,CAAC,OAAO,MAAM,KAAK,SAAS,QAAQ,GAAG;AAChD,UAAI,gBAAgB,SAAS,OAAO,SAAS,MAAM,GAAG;AACpD,wBAAgB;AAChB,iBAAS;AAAA,UACP,2BAA2B,QAAQ,CAAC,IAAI,SAAS,MAAM,sBAAsB,OAAO,SAAS,MAAM,eAAe,OAAO,SAAS,IAAI,MAChI,OAAO,WAAW,OAAO,KAAK,OAAO,WAAW,UAAU,WAAW,SAAS,OAAO,WAAW,KAAK,EAAE;AAAA,QAC/G;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,MAAM,UAAU,OAAO,UAAU,eAAe,YAAY,YAAY,IAAI,IAAI,MAAM;AACvG;AAEA,IAAM,YAAiC,CAAC,SAAS,cAAc,gBAAgB,gBAAgB,WAAW,gBAAgB;AAE1H,eAAsB,UAAU,SAA4C;AAC1E,QAAM,QAAQ,QAAQ,SAAS;AAC/B,wBAAsB,KAAK;AAE3B,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,QAAQ,IAAI,YAAY,SAAS;AACvC,MAAI;AACJ,QAAM,UAAU,QAAQ,OAAO,EAAE,UAAU,aAAa,MAAM,OAAO,GAAG,MAAM,CAAC;AAE/E,MAAI,QAAQ,sBAAsB,OAAO;AACvC,eAAW,CAAC,IAAI,OAAO,KAAK,OAAO,QAAQ,qBAAqB,EAAG,SAAQ,oBAAoB,IAAI,OAAO;AAAA,EAC5G;AAEA,QAAM,WAAW,IAAI,IAAI,QAAQ,YAAY,CAAC,CAAC;AAI/C,MAAI,QAAQ,sBAAsB,MAAO,UAAS,IAAI,gBAAgB;AACtE,QAAM,QAAQ,YAAY,IAAI;AAC9B,QAAM,UAAwB,CAAC;AAC/B,aAAW,QAAQ,OAAO;AACxB,cAAU;AACV,YAAQ,KAAK,MAAM,QAAQ,SAAS,OAAO,MAAM,WAAW,UAAU,QAAQ,kBAAkB,KAAK,CAAC;AAAA,EACxG;AACA,YAAU;AAEV,QAAM,aAAa,OAAO,YAAY,UAAU,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC,EAA4B,CAAC,CAAC,CAAC;AACxJ,QAAM,aAAgE,CAAC;AACvE,QAAM,mBAA2C,CAAC;AAClD,MAAI,SAAS;AACb,MAAI,cAAc;AAClB,MAAI,SAAS;AAEb,aAAW,UAAU,SAAS;AAC5B,QAAI,OAAO,YAAY,OAAW;AAClC,UAAM,QAAQ,WAAW,OAAO,KAAK,QAAQ;AAC7C,UAAM;AACN,UAAM,SAAqB,OAAO,MAAM,SAAS;AACjD,UAAM,QAAQ,MAAM,KAAK,MAAM,QAAQ,MAAM,KAAK,KAAK;AAEvD,UAAM,WAAY,WAAW,OAAO,KAAK,QAAQ,MAAM,EAAE,OAAO,GAAG,QAAQ,EAAE;AAC7E,aAAS;AAET,QAAI,OAAO,SAAS,WAAW,GAAG;AAChC;AACA,YAAM;AAAA,IACR,OAAO;AACL,YAAM;AACN,eAAS;AAAA,IACX;AAEA,UAAM,QAAQ,oBAAI,IAAY;AAC9B,eAAW,WAAW,OAAO,UAAU;AACrC,iBAAW,QAAQ,CAAC,GAAG,QAAQ,WAAW,UAAU,GAAG,QAAQ,WAAW,aAAa,EAAG,OAAM,IAAI,KAAK,QAAQ;AAAA,IACnH;AACA,eAAW,YAAY,MAAO,kBAAiB,QAAQ,KAAK,iBAAiB,QAAQ,KAAK,KAAK;AAE/F,QAAI,OAAO,KAAK,aAAa,WAAW,OAAO,KAAK,aAAa,kBAAkB;AACjF;AACA,UAAI,OAAO,MAAM,WAAW,QAAS;AAAA,IACvC;AAAA,EACF;AAEA,QAAM,YAAY,QAAQ,kBAAkB,EAAE,IAAI,CAAC,UAAU,MAAM,EAAE;AACrE,QAAM,uBAAuB,UAAU,OAAO,CAAC,OAAO,iBAAiB,EAAE,MAAM,MAAS;AAExF,QAAM,UAAU,QAAQ,OAAO,CAAC,WAAW,OAAO,YAAY,MAAS;AAEvE,SAAO;AAAA,IACL;AAAA,IACA,OAAO,QAAQ,SAAS,QAAQ;AAAA,IAChC;AAAA,IACA,QAAQ,QAAQ,SAAS,QAAQ,SAAS;AAAA,IAC1C,gBAAgB,QAAQ,OAAO,CAAC,WAAW,OAAO,aAAa;AAAA,IAC/D,oBAAoB,QAAQ,OAAO,CAAC,WAAW,OAAO,KAAK,aAAa,WAAW,OAAO,KAAK,iBAAiB,MAAS;AAAA,IACzH;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,kBAAkB,EAAE,OAAO,aAAa,OAAO;AAAA,IAC/C,YAAY,YAAY,IAAI,IAAI;AAAA,EAClC;AACF;;;ApB9VO,IAAM,SAAiC,OAAO,OAAO;AAAA,EAC1D,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL,CAAC;AAGM,IAAM,kBAAsD;AAAA,EACjE,OAAO;AAAA,EACP,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,SAAS;AAAA,EACT,gBAAgB;AAClB;AAGO,SAAS,sBAAsB,QAAgC,QAAc;AAClF,QAAM,OAAO,oBAAI,IAAyB;AAC1C,QAAM,WAAqB,CAAC;AAE5B,aAAW,QAAQ,OAAO;AACxB,UAAM,WAAW,KAAK,IAAI,KAAK,EAAE;AACjC,QAAI,SAAU,UAAS,KAAK,iBAAiB,KAAK,EAAE,MAAM,SAAS,KAAK,MAAM,KAAK,KAAK,GAAG;AAC3F,SAAK,IAAI,KAAK,IAAI,IAAI;AAEtB,QAAI,KAAK,SAAS,WAAW,EAAG,UAAS,KAAK,SAAS,KAAK,EAAE,mBAAmB;AACjF,QAAI,KAAK,WAAW,KAAK,EAAE,WAAW,EAAG,UAAS,KAAK,SAAS,KAAK,EAAE,qBAAqB;AAC5F,QAAI,CAAC,uBAAuB,KAAK,KAAK,EAAE,EAAG,UAAS,KAAK,YAAY,KAAK,EAAE,qBAAqB;AAAA,EACnG;AAEA,MAAI,SAAS,SAAS,EAAG,OAAM,IAAI,MAAM;AAAA,MAAmC,SAAS,KAAK,QAAQ,CAAC,EAAE;AACvG;AAEO,SAAS,gBAAgB,UAAoB,QAAgC,QAAuB;AACzG,SAAO,MAAM,OAAO,CAAC,SAAS,KAAK,aAAa,QAAQ;AAC1D;AAEO,SAAS,WAAW,KAAa,QAAgC,QAAuB;AAC7F,SAAO,MAAM,OAAO,CAAC,SAAS,KAAK,MAAM,SAAS,GAAG,MAAM,IAAI;AACjE;AAGO,SAAS,WAAW,QAAgC,QAAkB;AAC3E,SAAO,CAAC,GAAG,IAAI,IAAI,MAAM,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,EAAE,KAAK;AAC/D;","names":[]}
|