@luckystack/test-runner 0.14.0 → 0.15.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 +17 -0
- package/dist/contractCheck.d.ts +6 -0
- package/dist/customTests.d.ts +22 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +247 -126
- package/dist/index.js.map +1 -1
- package/dist/probeRequest.d.ts +2 -2
- package/dist/runAllTests.d.ts +19 -2
- package/dist/runAuthEnforcementTests.d.ts +3 -1
- package/dist/runContractTests.d.ts +15 -1
- package/dist/runFuzzTests.d.ts +9 -1
- package/dist/runRateLimitTests.d.ts +3 -1
- package/dist/testLayerHelpers.d.ts +12 -2
- package/dist/testSocket.d.ts +13 -0
- package/docs/contract-tests.md +7 -1
- package/docs/fuzz-tests.md +1 -1
- package/package.json +4 -4
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/walkEndpoints.ts","../src/probeRequest.ts","../src/contractCheck.ts","../src/testLayerHelpers.ts","../src/runContractTests.ts","../src/authEnforcementCheck.ts","../src/runAuthEnforcementTests.ts","../src/rateLimitCheck.ts","../src/resetServerState.ts","../src/runRateLimitTests.ts","../src/csrfEnforcementCheck.ts","../src/runCsrfEnforcementTests.ts","../src/fuzzCheck.ts","../src/runFuzzTests.ts","../src/socketProbeTransport.ts","../src/testSocket.ts","../src/schemaSampleInput.ts","../src/sampleInputFor.ts","../src/resolveTestBaseUrl.ts","../src/resolveTestEnvironment.ts","../src/testDataMarker.ts","../src/reporter.ts","../src/customTests.ts","../src/streamWatcher.ts","../src/runAllTests.ts","../src/extensionRegistry.ts","../src/runRegisteredLayers.ts"],"sourcesContent":["import type { EndpointDescriptor, HttpMethod, SyncMetaMap, SyncMethodMap } from './types';\n\ntype ApiMethodMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, string>>>>>>;\n\n//? Must stay in sync with the `HttpMethod` union in types.ts. Only values that\n//? appear in both sets are valid probes — anything else is an unrecognised method.\nconst VALID_HTTP_METHODS = new Set<HttpMethod>(['GET', 'POST', 'PUT', 'DELETE']);\n\nconst toHttpMethod = (raw: string): HttpMethod | null => {\n //? Guard against a stale or hand-edited generated map carrying an unrecognised\n //? method string. An unknown value would propagate silently as a typed HttpMethod\n //? and break every probe that uses it. Return null so the caller can skip it.\n const upper = raw.toUpperCase();\n for (const m of VALID_HTTP_METHODS) {\n if (m === upper) return m;\n }\n return null;\n};\n\nexport const walkEndpoints = (apiMethodMap: ApiMethodMap): EndpointDescriptor[] => {\n const endpoints: EndpointDescriptor[] = [];\n for (const [page, nameMap] of Object.entries(apiMethodMap)) {\n if (!nameMap) continue;\n for (const [name, versionMap] of Object.entries(nameMap)) {\n if (!versionMap) continue;\n for (const [version, rawMethod] of Object.entries(versionMap)) {\n if (rawMethod === undefined) continue;\n const method = toHttpMethod(rawMethod);\n if (!method) {\n //? Unrecognised method in the generated map — log and skip rather than\n //? silently coercing, which would produce invalid probes.\n console.warn(`[test-runner] walkEndpoints: skipping api/${page}/${name}/${version} — unrecognised method '${rawMethod}' in apiMethodMap`);\n continue;\n }\n endpoints.push({\n page,\n name,\n version,\n method,\n fullPath: `api/${page}/${name}/${version}`,\n });\n }\n }\n }\n return endpoints;\n};\n\n//? Walk a generated sync route map — the `syncMetaMap` devkit emits, or a\n//? `syncMethodMap`-shaped map — and produce a flat list of sync-route\n//? descriptors. Only the page → name → version keys matter; the leaf value is\n//? ignored. Each descriptor's `fullPath` is `sync/<page>/<name>/<version>` and\n//? its `kind` is `'sync'`. Sync routes always POST over the HTTP transport, so\n//? the method is hardcoded to `POST` here.\ntype SyncRouteMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, unknown>>>>>>;\n\nexport const walkSyncEndpoints = (syncMap: SyncMethodMap | SyncMetaMap | SyncRouteMap): EndpointDescriptor[] => {\n const endpoints: EndpointDescriptor[] = [];\n for (const [page, nameMap] of Object.entries(syncMap)) {\n if (!nameMap) continue;\n for (const [name, versionMap] of Object.entries(nameMap)) {\n if (!versionMap) continue;\n for (const [version, leaf] of Object.entries(versionMap)) {\n if (leaf === undefined) continue;\n endpoints.push({\n page,\n name,\n version,\n method: 'POST',\n fullPath: `sync/${page}/${name}/${version}`,\n kind: 'sync',\n });\n }\n }\n }\n return endpoints;\n};\n","//? Single-source probe used by every check layer (contract, auth, csrf,\n//? rate-limit, fuzz). Two levels:\n//? - `sendProbe` — one timed HTTP fetch + JSON-envelope parse. Centralises the\n//? AbortController + timeout + tryCatch pattern so it isn't copied into each\n//? check file.\n//? - `ProbeTransport` — \"send this payload to this endpoint\". The checks only\n//? know endpoints and payloads; the transport owns the wire: URL building,\n//? the GET query-string encoding, the sync `{ data, receiver }` envelope, and\n//? (socket transport, see `socketProbeTransport.ts`) Socket.io delivery.\n\nimport { buildRoutedGetUrl, tryCatch } from '@luckystack/core';\nimport type { EndpointDescriptor, HttpMethod } from './types';\n\nexport interface ProbeRequestInput {\n url: string;\n method: HttpMethod;\n baseUrl: string;\n body?: unknown;\n headers?: Record<string, string>;\n requestTimeoutMs: number;\n}\n\nexport interface ProbeResponse {\n /** HTTP status. Absent when the probe travelled over Socket.io. */\n httpStatus?: number;\n parsed: { status?: string; errorCode?: string } | null;\n}\n\n/**\n * Send a single timed fetch probe and parse the JSON envelope. Returns\n * `null` when the request itself fails (network error or abort).\n */\nexport const sendProbe = async (input: ProbeRequestInput): Promise<ProbeResponse | null> => {\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, input.requestTimeoutMs);\n\n //? A GET cannot carry a body (fetch throws), so its payload rides the query\n //? string as the reserved `__luckystack_data` value — the same encoding the\n //? real client sends and the server reads. Dropping it instead (the old\n //? behaviour) made every GET route with a required field fail validation.\n const isGet = input.method === 'GET';\n const url = isGet && input.body !== undefined ? buildRoutedGetUrl(input.url, input.body) : input.url;\n\n const [fetchError, response] = await tryCatch(() => fetch(url, {\n method: input.method,\n headers: {\n 'Content-Type': 'application/json',\n //? Browsers attach Origin on state-changing requests; the server's origin\n //? policy fail-closes POST/PUT/DELETE without one.\n 'Origin': new URL(input.baseUrl).origin,\n ...input.headers,\n },\n body: isGet ? undefined : JSON.stringify(input.body),\n signal: controller.signal,\n }));\n clearTimeout(timeoutHandle);\n\n if (fetchError || !response) return null;\n\n const [, parsed] = await tryCatch<{ status?: string; errorCode?: string } | null, undefined>(\n async () => await response.json() as { status?: string; errorCode?: string },\n );\n\n return { httpStatus: response.status, parsed: parsed ?? null };\n};\n\nexport interface ProbeTransportRequest {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n /** The route payload — the API `data`, or the sync `data` (the transport adds the sync envelope). */\n body: unknown;\n /** HTTP headers (session Cookie, CSRF). A socket transport reads only the Cookie. */\n headers?: Record<string, string>;\n requestTimeoutMs: number;\n}\n\nexport interface ProbeTransport {\n readonly name: 'http' | 'socket';\n /** Resolve with the parsed reply, or `null` when no reply arrived (network error / timeout). */\n send: (request: ProbeTransportRequest) => Promise<ProbeResponse | null>;\n /** Release held connections. Optional — the HTTP transport holds none. */\n close?: () => Promise<void>;\n}\n\nexport interface HttpProbeTransportOptions {\n /**\n * Room sent as `receiver` on sync probes. Without one the server answers\n * `sync.missingReceiver`, which proves the envelope but not the route.\n */\n syncReceiver?: string;\n}\n\n//? HTTP transport: API routes go to `/api/...` with the payload as the body (or\n//? query string for GET); sync routes go to `/sync/...` wrapped in the same\n//? `{ data, receiver }` envelope the routed-HTTP client sends.\nexport const createHttpProbeTransport = (options: HttpProbeTransportOptions = {}): ProbeTransport => ({\n name: 'http',\n send: (request) => sendProbe({\n url: `${request.baseUrl.replace(/\\/$/, '')}/${request.endpoint.fullPath}`,\n method: request.endpoint.method,\n baseUrl: request.baseUrl,\n body: request.endpoint.kind === 'sync'\n ? { data: request.body, receiver: options.syncReceiver }\n : request.body,\n headers: request.headers,\n requestTimeoutMs: request.requestTimeoutMs,\n }),\n});\n\n/** Transport the checks use when the caller supplies none. */\nexport const defaultProbeTransport: ProbeTransport = createHttpProbeTransport();\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { defaultProbeTransport } from './probeRequest';\nimport type { ProbeTransport } from './probeRequest';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\n\nexport interface ContractCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n /**\n * Optional input factory. Defaults to `{}`. When an endpoint requires a\n * specific shape to not crash, callers can override per-endpoint here.\n */\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n /**\n * Optional headers (auth tokens, cookies). Applied to every request.\n */\n headers?: Record<string, string>;\n /**\n * Per-call request timeout in ms. Defaults to 5000. Bump for slow endpoints\n * (AI calls, large reports) where the framework default would false-fail\n * the contract check.\n */\n requestTimeoutMs?: number;\n /** How the probe reaches the server. Defaults to HTTP. */\n transport?: ProbeTransport;\n}\n\n//? Contract layer: send any-shape input, accept any response where\n//? `status` is `success` or `error` (with an `errorCode`). Anything else is\n//? a contract violation — 5xx, thrown exceptions, or shapes outside this\n//? envelope.\n//?\n//? The goal here is NOT to prove every endpoint works end-to-end; it's to\n//? catch endpoints that crash the server or return garbage on well-formed\n//? empty input. Real behavior tests go in the per-endpoint test file.\nexport const runContractCheck = async (input: ContractCheckInput): Promise<ContractCheckResult> => {\n const { endpoint, baseUrl } = input;\n const body = input.inputFor ? input.inputFor(endpoint) : {};\n const started = Date.now();\n\n const result = await (input.transport ?? defaultProbeTransport).send({\n endpoint,\n baseUrl,\n body,\n headers: input.headers,\n requestTimeoutMs: input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n });\n\n if (!result) {\n return {\n endpoint,\n status: 'fail',\n reason: 'fetch returned no response',\n durationMs: Date.now() - started,\n };\n }\n\n const { httpStatus, parsed } = result;\n const durationMs = Date.now() - started;\n\n if (!parsed) {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'unknown',\n reason: 'JSON parse failed',\n durationMs,\n };\n }\n\n if (parsed.status !== 'success' && parsed.status !== 'error') {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'unknown',\n reason: 'Response missing standard `status` envelope',\n durationMs,\n };\n }\n\n if (parsed.status === 'error' && !parsed.errorCode) {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'error',\n reason: 'Error response missing `errorCode`',\n durationMs,\n };\n }\n\n //? When `inputFor` produced a non-empty sample body but the route returned an\n //? error, the probe may have failed its own Zod validation rather than proving\n //? the contract. Counting that as a PASS is misleading — the happy-path was\n //? never exercised. Classify as `skipped` so the caller knows the body was\n //? rejected before the handler ran and can supply a better sample.\n const bodyIsNonEmpty = body !== null\n && typeof body === 'object'\n && !Array.isArray(body)\n && Object.keys(body).length > 0;\n if (input.inputFor && bodyIsNonEmpty && parsed.status === 'error') {\n return {\n endpoint,\n status: 'skipped',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n reason: `inputFor produced a non-empty sample but route returned error (${parsed.errorCode ?? 'unknown'}); provide a valid sample via inputFor to test the happy path`,\n durationMs,\n };\n }\n\n return {\n endpoint,\n status: 'pass',\n httpStatus,\n responseStatus: parsed.status,\n errorCode: parsed.errorCode,\n durationMs,\n };\n};\n","//? Shared helpers for the sweep test layers (contract, auth-enforcement,\n//? rate-limit, fuzz). Centralizes the endpoint selection, skip-matching,\n//? meta-map queries, and summary-count math that were previously copy-pasted\n//? across each layer file.\n\nimport { walkEndpoints, walkSyncEndpoints } from './walkEndpoints';\nimport { defaultProbeTransport } from './probeRequest';\nimport type { ProbeTransport } from './probeRequest';\nimport type {\n ApiMethodMap,\n ApiMetaMap,\n ContractCheckResult,\n EndpointDescriptor,\n HttpMethod,\n RouteMetaMap,\n RunContractSummary,\n SyncMetaMap,\n} from './types';\n\n//? Single source of truth for the canonical layer names used in both the\n//? orchestrator (`runAllTests`) and the reporter (`logRunAllSummary`). Keeping\n//? them here prevents silent drift between the reporting labels and the summary\n//? property keys when layers are added or renamed.\nexport const LAYER_KEYS = {\n contract: 'contract',\n auth: 'auth-enforcement',\n rateLimit: 'rate-limit',\n csrf: 'csrf-enforcement',\n fuzz: 'fuzz',\n custom: 'custom',\n} as const;\n\n//? HTTP methods that carry a body and mutate server state. The CSRF middleware\n//? enforces a token only on these. Fuzz / rate-limit layers share this set to\n//? avoid firing junk bodies at mutating authenticated routes in cookie-mode.\nexport const STATE_CHANGING_METHODS = new Set<HttpMethod>(['POST', 'PUT', 'DELETE']);\n\n/** `<page>/<name>/<version>` — the key `filter` and `skip` match against. */\nexport const routeKey = (endpoint: EndpointDescriptor): string =>\n `${endpoint.page}/${endpoint.name}/${endpoint.version}`;\n\n/** Whether an endpoint matches a substring `filter` (no filter matches all). */\nexport const matchesFilter = (endpoint: EndpointDescriptor, filter: string | undefined): boolean =>\n !filter || routeKey(endpoint).includes(filter);\n\nexport interface SelectEndpointsInput {\n apiMethodMap: ApiMethodMap;\n /** Generated `syncMetaMap`. When given, sync routes are swept too. */\n syncMetaMap?: SyncMetaMap;\n /** Substring filter on `<page>/<name>/<version>`. Non-matching routes are never requested. */\n filter?: string;\n}\n\n/**\n * The endpoints a sweep layer exercises: every API route, plus every sync\n * route when a `syncMetaMap` is supplied, narrowed by `filter`. Filtering\n * here — before any request — is what makes `filter` limit the traffic, not\n * just the report.\n */\nexport const selectEndpoints = (input: SelectEndpointsInput): EndpointDescriptor[] => [\n ...walkEndpoints(input.apiMethodMap),\n ...(input.syncMetaMap ? walkSyncEndpoints(input.syncMetaMap) : []),\n].filter((endpoint) => matchesFilter(endpoint, input.filter));\n\n/** The meta map that describes `endpoint`: `syncMetaMap` for sync routes, else `apiMetaMap`. */\nexport const metaMapFor = (\n endpoint: EndpointDescriptor,\n apiMetaMap: ApiMetaMap,\n syncMetaMap: SyncMetaMap | undefined,\n): RouteMetaMap => (endpoint.kind === 'sync' ? syncMetaMap ?? {} : apiMetaMap);\n\n/** The transport for `endpoint`: `syncTransport` for sync routes when given, else HTTP. */\nexport const transportFor = (\n endpoint: EndpointDescriptor,\n syncTransport: ProbeTransport | undefined,\n): ProbeTransport => (endpoint.kind === 'sync' && syncTransport ? syncTransport : defaultProbeTransport);\n\n/**\n * Whether an endpoint is in the explicit skip list. Matched against\n * `<page>/<name>` (version-agnostic) and `<page>/<name>/<version>`\n * (version-specific).\n */\nexport const shouldSkip = (endpoint: EndpointDescriptor, skip: string[]): boolean => {\n if (skip.length === 0) return false;\n const versioned = routeKey(endpoint);\n const versionless = `${endpoint.page}/${endpoint.name}`;\n return skip.includes(versioned) || skip.includes(versionless);\n};\n\n/** Whether the endpoint's meta entry declares `auth.login: true`. */\nexport const requiresLogin = (metaMap: RouteMetaMap, endpoint: EndpointDescriptor): boolean => {\n const meta = metaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version];\n //? The meta map is a consumer-generated RUNTIME artifact — the static type\n //? declares `auth` as required, but a stale/partial entry can be missing it.\n //? Read defensively so a malformed entry surfaces as `false` (no login) and\n //? gets handled by the layer's skip path, instead of throwing a TypeError\n //? outside the per-endpoint tryCatch and aborting the whole layer.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime-defensive read of a generated artifact\n return meta?.auth?.login ?? false;\n};\n\n/** Whether the endpoint requires ANY authorization: `auth.login: true` OR a\n * declared non-empty `auth.additional[]` predicate list (role/tenant/ownership\n * guards). The auth sweep must probe BOTH — an additional[]-only route (login:\n * false) is still authz-protected and an unauth call must be rejected. */\nexport const hasAuthRequirement = (metaMap: RouteMetaMap, endpoint: EndpointDescriptor): boolean => {\n const meta = metaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version];\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime-defensive read of a generated artifact\n return (meta?.auth?.login ?? false) || (meta?.auth?.hasAdditional ?? false);\n};\n\n/** Whether the meta map carries an entry for this endpoint at all. */\nexport const hasMetaEntry = (metaMap: RouteMetaMap, endpoint: EndpointDescriptor): boolean =>\n metaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version] !== undefined;\n\n/**\n * The endpoint's declared numeric rate limit, or `null` when it has none\n * (`false`/`undefined`) or no meta entry exists.\n */\nexport const getRateLimit = (metaMap: RouteMetaMap, endpoint: EndpointDescriptor): number | null => {\n const meta = metaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version];\n if (!meta || meta.rateLimit === false || meta.rateLimit === undefined) return null;\n return meta.rateLimit;\n};\n\n/** Aggregate pass/fail/skip counts over a sweep layer's results. */\nexport const calculateSummary = (results: ContractCheckResult[]): RunContractSummary => ({\n total: results.length,\n passed: results.filter(r => r.status === 'pass').length,\n failed: results.filter(r => r.status === 'fail').length,\n skipped: results.filter(r => r.status === 'skipped').length,\n results,\n});\n","import { runContractCheck } from './contractCheck';\nimport { shouldSkip, calculateSummary, selectEndpoints, transportFor } from './testLayerHelpers';\nimport type { ProbeTransport } from './probeRequest';\nimport type { ApiMethodMap, ContractCheckResult, EndpointDescriptor, RunContractSummary, SyncMetaMap } from './types';\n\nexport interface RunContractTestsInput {\n apiMethodMap: ApiMethodMap;\n /** Generated `syncMetaMap`. When given, sync routes are swept too. */\n syncMetaMap?: SyncMetaMap;\n baseUrl: string;\n /**\n * Endpoints to skip. Matched against `<page>/<name>` (version-agnostic) and\n * `<page>/<name>/<version>` (version-specific). Useful for endpoints that\n * are known to need real input (e.g. a file upload).\n */\n skip?: string[];\n /** Substring filter on `<page>/<name>/<version>`; other routes are not requested at all. */\n filter?: string;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n headers?: Record<string, string>;\n /** Transport for sync routes (e.g. the socket transport). API routes always use HTTP. */\n syncTransport?: ProbeTransport;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runContractTests = async (input: RunContractTestsInput): Promise<RunContractSummary> => {\n const endpoints = selectEndpoints(input);\n const skip = input.skip ?? [];\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n //? Serial rather than parallel so we don't trip rate limiters or mask\n //? endpoint ordering bugs. Swap to a pool later if walk-time matters.\n const result = await runContractCheck({\n endpoint,\n baseUrl: input.baseUrl,\n inputFor: input.inputFor,\n headers: input.headers,\n transport: transportFor(endpoint, input.syncTransport),\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { defaultProbeTransport } from './probeRequest';\nimport type { ProbeTransport } from './probeRequest';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\n\n//? Canonical error codes the framework emits when an unauthenticated request hits\n//? a protected endpoint: `auth.required` for an `auth.login: true` route, and\n//? `auth.forbidden` for a route guarded ONLY by `auth.additional[]` predicates\n//? (login: false) — validateRequest forbids the anonymous caller. Either is a PASS;\n//? both mean the guard fired. See packages/api/src/handleHttpApiRequest.ts +\n//? packages/core/src/validateRequest.ts.\nconst EXPECTED_ERROR_CODES = new Set(['auth.required', 'auth.forbidden']);\n\nexport interface AuthEnforcementCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n /** Per-call request timeout in ms. Defaults to 5000. */\n requestTimeoutMs?: number;\n /** How the probe reaches the server. Defaults to HTTP. */\n transport?: ProbeTransport;\n}\n\n//? Auth-enforcement layer: for each `auth.login: true` endpoint, call it with\n//? NO session headers/cookies. Expect the server to reject with `auth.required`.\n//?\n//? A passing endpoint here proves the login guard isn't silently bypassed.\n//? Endpoints that return `success` without a session are a critical finding.\nexport const runAuthEnforcementCheck = async (\n input: AuthEnforcementCheckInput,\n): Promise<ContractCheckResult> => {\n const { endpoint, baseUrl } = input;\n const body = input.inputFor ? input.inputFor(endpoint) : {};\n const started = Date.now();\n\n //? No session cookie, no auth header — deliberately (over a socket transport:\n //? an anonymous socket). Origin IS sent so the server's origin policy doesn't\n //? 403 before the auth check can run.\n const result = await (input.transport ?? defaultProbeTransport).send({\n endpoint,\n baseUrl,\n body,\n requestTimeoutMs: input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n });\n\n if (!result) {\n return {\n endpoint,\n status: 'fail',\n reason: 'fetch returned no response',\n durationMs: Date.now() - started,\n };\n }\n\n const { httpStatus, parsed } = result;\n const durationMs = Date.now() - started;\n\n if (parsed?.status === 'success') {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'success',\n reason: 'auth.login endpoint returned success without a session',\n durationMs,\n };\n }\n\n if (parsed?.status !== 'error') {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'unknown',\n reason: 'Response missing standard `status` envelope',\n durationMs,\n };\n }\n\n if (parsed.errorCode === undefined || !EXPECTED_ERROR_CODES.has(parsed.errorCode)) {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n reason: `expected an auth-rejection errorCode (${[...EXPECTED_ERROR_CODES].join(' | ')}) but got '${parsed.errorCode ?? '(missing)'}'`,\n durationMs,\n };\n }\n\n return {\n endpoint,\n status: 'pass',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n durationMs,\n };\n};\n","import { runAuthEnforcementCheck } from './authEnforcementCheck';\nimport {\n shouldSkip,\n hasAuthRequirement,\n hasMetaEntry,\n calculateSummary,\n metaMapFor,\n selectEndpoints,\n transportFor,\n} from './testLayerHelpers';\nimport type { ProbeTransport } from './probeRequest';\nimport type {\n ApiMethodMap,\n ApiMetaMap,\n ContractCheckResult,\n EndpointDescriptor,\n RunContractSummary,\n SyncMetaMap,\n} from './types';\n\nexport interface RunAuthEnforcementTestsInput {\n apiMethodMap: ApiMethodMap;\n apiMetaMap: ApiMetaMap;\n /** Generated `syncMetaMap`. When given, sync routes are swept too. */\n syncMetaMap?: SyncMetaMap;\n baseUrl: string;\n skip?: string[];\n /** Substring filter on `<page>/<name>/<version>`; other routes are not requested at all. */\n filter?: string;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n /** Transport for sync routes (e.g. the socket transport). API routes always use HTTP. */\n syncTransport?: ProbeTransport;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runAuthEnforcementTests = async (\n input: RunAuthEnforcementTestsInput,\n): Promise<RunContractSummary> => {\n const endpoints = selectEndpoints(input);\n const skip = input.skip ?? [];\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n const metaMap = metaMapFor(endpoint, input.apiMetaMap, input.syncMetaMap);\n //? Order matters: the auth-requirement check must run BEFORE shouldSkip so a\n //? protected endpoint in the explicit skip list is still recorded as\n //? `skipped` in the results (not silently dropped as a public route).\n //? Reversing the order would hide the skip from the summary. We gate on\n //? `hasAuthRequirement` (login OR additional[]-predicates) so role/tenant/\n //? ownership-guarded routes with `login: false` are also probed.\n if (!hasMetaEntry(metaMap, endpoint)) {\n //? Route is in the method/sync map but ABSENT from its meta map (partial\n //? drift / hand-trimmed map / generator bug). Its auth requirement is\n //? unknowable, so this layer can't assert it. Record a `skipped` result (NOT\n //? a silent continue) so the gap is visible per-route in the summary instead\n //? of the route masquerading as a verified-public endpoint. (M8)\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: `No ${endpoint.kind === 'sync' ? 'syncMetaMap' : 'apiMetaMap'} entry — auth requirement unverifiable`,\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (!hasAuthRequirement(metaMap, endpoint)) {\n //? Truly-public endpoints (meta entry present, login:false, no additional[])\n //? can't be tested by this layer — skip silently, no noise. They're still\n //? covered by the contract layer.\n continue;\n }\n\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n const result = await runAuthEnforcementCheck({\n endpoint,\n baseUrl: input.baseUrl,\n inputFor: input.inputFor,\n transport: transportFor(endpoint, input.syncTransport),\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { defaultProbeTransport } from './probeRequest';\nimport type { ProbeTransport } from './probeRequest';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 10_000;\n\n//? The limiter's rejection code is namespaced by route kind: `api.*` from the\n//? API pipeline, `sync.*` from both sync pipelines (socket + HTTP).\nconst expectedErrorCodeFor = (endpoint: EndpointDescriptor): string =>\n endpoint.kind === 'sync' ? 'sync.rateLimitExceeded' : 'api.rateLimitExceeded';\n\n//? Error codes the server emits BEFORE the rate-limit pipeline runs (auth guard\n//? + server-level CSRF middleware). When a drain response carries one of these,\n//? the bucket was never incremented — the final probe will fail for the wrong\n//? reason and should be classified as `skipped` rather than a real failure.\n//? These MUST match the framework's actual pre-limiter codes (pipeline order:\n//? auth → rateLimit, CSRF middleware before the limiter):\n//? - `auth.required` — login:true route called without a session\n//? (handleHttpApiRequest.ts).\n//? - `auth.forbidden` — additional[]-predicate failed / login:false guard\n//? (handleHttpApiRequest.ts).\n//? - `auth.csrfMismatch` — cookie-mode state-changing route, CSRF gate\n//? (server/httpRoutes/csrfMiddleware.ts).\n//? - `sync.notRoomMember` / `sync.receiverNotAllowed` / `sync.missingReceiver`\n//? — sync receiver checks, which run after auth and before the limiter\n//? (handleSyncRequest.ts / handleHttpSyncRequest.ts).\n//? Validation (`api.invalidInputType`) runs AFTER the limiter, so it is NOT a\n//? pre-limiter code and is deliberately excluded.\nconst PRE_LIMITER_ERROR_CODES = new Set([\n 'auth.required',\n 'auth.forbidden',\n 'auth.csrfMismatch',\n 'sync.notRoomMember',\n 'sync.receiverNotAllowed',\n 'sync.missingReceiver',\n]);\n\nexport interface RateLimitCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n rateLimit: number;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n headers?: Record<string, string>;\n /** Per-call request timeout in ms. Defaults to 10000. */\n requestTimeoutMs?: number;\n /** How the probes reach the server. Defaults to HTTP. */\n transport?: ProbeTransport;\n}\n\n//? Rate-limit layer: fire `rateLimit + 1` requests back to back and assert the\n//? last one is rejected with `api.rateLimitExceeded` (`sync.rateLimitExceeded`\n//? for a sync route). All preceding requests are allowed to return either\n//? success or any other envelope — only the N+1 matters for this layer.\n//?\n//? This layer mutates server-side state (limiter counters). Run it after the\n//? contract + auth layers, or against a dedicated test instance. The server's\n//? clearAllRateLimits() utility can reset state between runs.\nexport const runRateLimitCheck = async (input: RateLimitCheckInput): Promise<ContractCheckResult> => {\n const { endpoint, baseUrl, rateLimit } = input;\n const body = input.inputFor ? input.inputFor(endpoint) : {};\n const started = Date.now();\n const requestTimeoutMs = input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const expectedErrorCode = expectedErrorCodeFor(endpoint);\n const transport = input.transport ?? defaultProbeTransport;\n\n const send = () => transport.send({\n endpoint,\n baseUrl,\n body,\n headers: input.headers,\n requestTimeoutMs,\n });\n\n //? Fire `rateLimit` allowed requests first (drain the bucket), then a final\n //? request that should be blocked. Serial so the server sees a clean order.\n //? Inspect EVERY drain response for pre-limiter rejections (CSRF/auth/\n //? validation): if the server rejects before the limiter runs at any point,\n //? the bucket was never incremented for that request, the final probe will\n //? fail for the wrong reason, and we classify the check as `skipped`.\n for (let i = 0; i < rateLimit; i += 1) {\n const drained = await send();\n if (!drained) continue;\n const errorCode = drained.parsed?.errorCode;\n if (errorCode && PRE_LIMITER_ERROR_CODES.has(errorCode)) {\n return {\n endpoint,\n status: 'skipped',\n durationMs: Date.now() - started,\n reason: `drain request ${i + 1} rejected pre-limiter (${errorCode}); bucket was never incremented — check CSRF/auth headers`,\n };\n }\n }\n\n const final = await send();\n const durationMs = Date.now() - started;\n\n if (!final) {\n return {\n endpoint,\n status: 'fail',\n reason: 'Final rate-limit probe request failed to return a response',\n durationMs,\n };\n }\n\n const { httpStatus, parsed } = final;\n\n if (parsed?.status === 'error' && parsed.errorCode === expectedErrorCode) {\n return {\n endpoint,\n status: 'pass',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n durationMs,\n };\n }\n\n const responseStatus: ContractCheckResult['responseStatus'] =\n parsed?.status === 'success' || parsed?.status === 'error' ? parsed.status : 'unknown';\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus,\n errorCode: parsed?.errorCode,\n reason: `expected '${expectedErrorCode}' on request ${rateLimit + 1} but got ${parsed?.status ?? 'none'}/${parsed?.errorCode ?? '(none)'}`,\n durationMs,\n };\n};\n","import { getProjectConfig, TEST_RESET_ONLY_PARAM, tryCatch } from '@luckystack/core';\nimport type { TestResetTarget } from '@luckystack/core';\n\n//? Calls the server's /_test/reset endpoint. Used between rate-limit test\n//? endpoints to drain the shared IP-based limiter bucket so one endpoint's\n//? N+1 assertion doesn't consume the next endpoint's window.\n//?\n//? /_test/reset is gated by `NODE_ENV in { 'development', 'test' }` on the\n//? server side AND requires `TEST_RESET_TOKEN` to be set unconditionally\n//? (no token = 403). For staging/preview deploys that enable it for CI,\n//? set `TEST_RESET_TOKEN` on the server and pass the same value here.\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 10_000;\n\nexport interface ResetServerStateInput {\n baseUrl: string;\n token?: string;\n requestTimeoutMs?: number;\n /**\n * Clear only these targets (`?only=`). Omit for the server's default reset,\n * which ALSO deletes every Redis session — including an authenticated sweep's\n * own. The runner itself always passes `['rateLimits']`.\n */\n only?: TestResetTarget[];\n}\n\nexport const resetServerState = async ({ baseUrl, token, requestTimeoutMs, only }: ResetServerStateInput): Promise<boolean> => {\n const endpoint = `${baseUrl.replace(/\\/$/, '')}${getProjectConfig().http.testResetEndpoint}`;\n const url = only && only.length > 0\n ? `${endpoint}?${new URLSearchParams({ [TEST_RESET_ONLY_PARAM]: only.join(',') }).toString()}`\n : endpoint;\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (token) headers['X-Test-Reset-Token'] = token;\n\n //? Bound the call so a server that accepts the connection but never responds\n //? returns `false` (the existing failure path) instead of wedging the sweep.\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS);\n const [error, response] = await tryCatch(() => fetch(url, { method: 'POST', headers, signal: controller.signal }));\n clearTimeout(timeoutHandle);\n if (error || !response) return false;\n return response.ok;\n};\n","import { runRateLimitCheck } from './rateLimitCheck';\nimport { resetServerState } from './resetServerState';\nimport {\n shouldSkip,\n requiresLogin,\n getRateLimit,\n calculateSummary,\n metaMapFor,\n selectEndpoints,\n transportFor,\n STATE_CHANGING_METHODS,\n} from './testLayerHelpers';\nimport type { ProbeTransport } from './probeRequest';\nimport type {\n ApiMethodMap,\n ApiMetaMap,\n ContractCheckResult,\n EndpointDescriptor,\n RunContractSummary,\n SyncMetaMap,\n} from './types';\n\nexport interface RunRateLimitTestsInput {\n apiMethodMap: ApiMethodMap;\n apiMetaMap: ApiMetaMap;\n /** Generated `syncMetaMap`. When given, sync routes are swept too. */\n syncMetaMap?: SyncMetaMap;\n baseUrl: string;\n skip?: string[];\n /** Substring filter on `<page>/<name>/<version>`; other routes are not requested at all. */\n filter?: string;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n headers?: Record<string, string>;\n /** Transport for sync routes (e.g. the socket transport). API routes always use HTTP. */\n syncTransport?: ProbeTransport;\n /**\n * Max rateLimit value to test. Endpoints with a higher limit are skipped\n * to avoid firing thousands of requests in CI.\n */\n maxRateLimitToTest?: number;\n /**\n * When true, hit `/_test/reset?only=rateLimits` before each endpoint so\n * limiter state is clean. Only the limiter buckets are cleared — sessions\n * survive, so an authenticated sweep keeps its session. Requires the server\n * to allow the endpoint: `NODE_ENV` in { 'development', 'test' } AND\n * `TEST_RESET_TOKEN` set on the server with a matching `resetToken` here (an\n * unset token is 403, never open). Without resetting, the shared IP bucket\n * leaks state between endpoints.\n */\n resetBetweenEndpoints?: boolean;\n /**\n * Token forwarded as `X-Test-Reset-Token` when resetting. Only relevant in\n * staging/preview deploys that enable the endpoint behind a token.\n */\n resetToken?: string;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runRateLimitTests = async (\n input: RunRateLimitTestsInput,\n): Promise<RunContractSummary> => {\n const endpoints = selectEndpoints(input);\n const skip = input.skip ?? [];\n const maxRateLimit = input.maxRateLimitToTest ?? 50;\n //? A login-required route returns `auth.required` BEFORE the rate limiter\n //? runs, so it can only be rate-limit-tested with a session. A Cookie header\n //? means an authenticated sweep. We deliberately do NOT auto-authenticate the\n //? sweep — that would execute real mutations (delete/update) with junk input.\n //? Set TEST_AUTH_TOKEN to opt in and cover login-gated routes.\n const isAuthenticatedSweep = Boolean(input.headers?.Cookie);\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n const metaMap = metaMapFor(endpoint, input.apiMetaMap, input.syncMetaMap);\n const rateLimit = getRateLimit(metaMap, endpoint);\n if (rateLimit === null) continue;\n\n if (!isAuthenticatedSweep && requiresLogin(metaMap, endpoint)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'login-required route — set TEST_AUTH_TOKEN to rate-limit-test it (unauthenticated calls hit auth.required before the limiter)',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n //? In cookie-mode the rate-limit check fires the endpoint `rateLimit + 1`\n //? times with junk input. State-changing routes (POST/PUT/DELETE — every\n //? sync route) execute real mutation handlers on each hit — skip them to\n //? avoid side-effects on the test database. Their rate limits are better\n //? covered by a custom test that supplies valid input.\n if (isAuthenticatedSweep && STATE_CHANGING_METHODS.has(endpoint.method)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'state-changing route skipped in cookie-mode rate-limit sweep (mutation risk with junk bodies)',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (rateLimit > maxRateLimit) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: `rateLimit ${rateLimit} exceeds maxRateLimitToTest=${maxRateLimit}`,\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (input.resetBetweenEndpoints) {\n //? Limiter buckets only: a full reset also deletes every Redis session,\n //? including the one an authenticated sweep is running on.\n await resetServerState({ baseUrl: input.baseUrl, token: input.resetToken, only: ['rateLimits'] });\n }\n\n const result = await runRateLimitCheck({\n endpoint,\n baseUrl: input.baseUrl,\n rateLimit,\n inputFor: input.inputFor,\n headers: input.headers,\n transport: transportFor(endpoint, input.syncTransport),\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { defaultProbeTransport } from './probeRequest';\nimport type { ProbeTransport } from './probeRequest';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\n\n//? Canonical error code the framework's CSRF middleware emits when a\n//? state-changing, cookie-authenticated request arrives without a matching\n//? CSRF token. See packages/server/src/httpRoutes/csrfMiddleware.ts.\nconst DEFAULT_EXPECTED_ERROR_CODE = 'auth.csrfMismatch';\nconst DEFAULT_EXPECTED_HTTP_STATUS = 403;\n\nexport interface CsrfEnforcementCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n /**\n * A valid session Cookie header (`<name>=<token>`). The probe deliberately\n * carries it so the request passes the auth guard and reaches the CSRF check.\n */\n authCookie: string;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n /** Per-call request timeout in ms. Defaults to 5000. */\n requestTimeoutMs?: number;\n /** Error code expected on CSRF rejection. Defaults to `auth.csrfMismatch`. */\n expectedErrorCode?: string;\n /** HTTP status expected on CSRF rejection. Defaults to 403; pass `false` to skip. */\n expectedHttpStatus?: number | false;\n /**\n * HTTP transport the probe uses (e.g. one carrying the sync `receiver`).\n * CSRF is an HTTP-only guard, so a non-HTTP transport makes the check skip.\n * Defaults to plain HTTP.\n */\n transport?: ProbeTransport;\n}\n\n//? CSRF-enforcement layer: for each `auth.login: true` state-changing route,\n//? send an authenticated request (valid session Cookie) WITHOUT the CSRF\n//? header. Expect the framework's CSRF middleware to reject it. This is the\n//? inverse of the custom layer's CSRF token-passing — it proves the protection\n//? actually fires, so a consumer who misconfigures CSRF doesn't get a green run.\nexport const runCsrfEnforcementCheck = async (\n input: CsrfEnforcementCheckInput,\n): Promise<ContractCheckResult> => {\n const { endpoint, baseUrl } = input;\n //? CSRF is only enforced on state-changing methods (POST/PUT/DELETE). A GET\n //? probe would never reach the CSRF middleware regardless of headers, so it\n //? would always appear to pass — a false green. Guard here so the check is\n //? safe to call directly (the runner already filters, but direct callers may not).\n if (endpoint.method === 'GET') {\n return {\n endpoint,\n status: 'skipped',\n reason: `CSRF enforcement does not apply to ${endpoint.method} requests`,\n durationMs: 0,\n };\n }\n const transport = input.transport ?? defaultProbeTransport;\n if (transport.name !== 'http') {\n return {\n endpoint,\n status: 'skipped',\n reason: `CSRF enforcement only applies over HTTP (transport: ${transport.name})`,\n durationMs: 0,\n };\n }\n const body = input.inputFor ? input.inputFor(endpoint) : {};\n const started = Date.now();\n const expectedErrorCode = input.expectedErrorCode ?? DEFAULT_EXPECTED_ERROR_CODE;\n const expectedHttpStatus = input.expectedHttpStatus ?? DEFAULT_EXPECTED_HTTP_STATUS;\n\n //? Valid session Cookie + Origin, but NO CSRF header — that omission is the\n //? whole point of the probe.\n const result = await transport.send({\n endpoint,\n baseUrl,\n body,\n headers: { Cookie: input.authCookie },\n requestTimeoutMs: input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n });\n\n if (!result) {\n return { endpoint, status: 'fail', reason: 'fetch returned no response', durationMs: Date.now() - started };\n }\n\n const { httpStatus, parsed } = result;\n const durationMs = Date.now() - started;\n\n if (parsed?.status === 'success') {\n return {\n endpoint, status: 'fail', httpStatus, responseStatus: 'success',\n reason: 'state-changing endpoint accepted an authenticated request with NO CSRF header — CSRF protection is not enforced',\n durationMs,\n };\n }\n\n if (parsed?.status !== 'error' || parsed.errorCode !== expectedErrorCode) {\n const responseStatus: ContractCheckResult['responseStatus'] =\n parsed?.status === 'success' || parsed?.status === 'error' ? parsed.status : 'unknown';\n return {\n endpoint, status: 'fail', httpStatus, responseStatus, errorCode: parsed?.errorCode,\n reason: `expected CSRF rejection '${expectedErrorCode}' but got ${parsed?.status ?? 'none'}/${parsed?.errorCode ?? '(none)'}`,\n durationMs,\n };\n }\n\n if (expectedHttpStatus !== false && httpStatus !== expectedHttpStatus) {\n return {\n endpoint, status: 'fail', httpStatus, responseStatus: 'error', errorCode: parsed.errorCode,\n reason: `expected HTTP ${expectedHttpStatus} on CSRF rejection but got ${httpStatus}`,\n durationMs,\n };\n }\n\n return { endpoint, status: 'pass', httpStatus, responseStatus: 'error', errorCode: parsed.errorCode, durationMs };\n};\n","import { runCsrfEnforcementCheck } from './csrfEnforcementCheck';\nimport {\n shouldSkip,\n requiresLogin,\n hasMetaEntry,\n calculateSummary,\n metaMapFor,\n selectEndpoints,\n transportFor,\n STATE_CHANGING_METHODS,\n} from './testLayerHelpers';\nimport type { ProbeTransport } from './probeRequest';\nimport type {\n ApiMethodMap,\n ApiMetaMap,\n ContractCheckResult,\n EndpointDescriptor,\n RunContractSummary,\n SyncMetaMap,\n} from './types';\n\nexport interface RunCsrfEnforcementTestsInput {\n apiMethodMap: ApiMethodMap;\n apiMetaMap: ApiMetaMap;\n /** Generated `syncMetaMap`. When given, sync routes' `/sync/*` HTTP endpoints are probed too. */\n syncMetaMap?: SyncMetaMap;\n baseUrl: string;\n /** Valid session Cookie header (`<name>=<token>`) the probes carry so they reach the CSRF check. */\n authCookie: string;\n skip?: string[];\n /** Substring filter on `<page>/<name>/<version>`; other routes are not requested at all. */\n filter?: string;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n /**\n * HTTP transport for sync routes (one carrying the sync `receiver`). CSRF is\n * an HTTP-only guard, so this must be an HTTP transport even when the project\n * syncs over Socket.io — the `/sync/*` HTTP route is reachable either way.\n */\n syncTransport?: ProbeTransport;\n /** Per-call request timeout in ms forwarded to each check. */\n requestTimeoutMs?: number;\n /** Error code expected on CSRF rejection. Defaults to `auth.csrfMismatch`. */\n expectedErrorCode?: string;\n /** HTTP status expected on CSRF rejection. Defaults to 403; pass `false` to skip. */\n expectedHttpStatus?: number | false;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runCsrfEnforcementTests = async (\n input: RunCsrfEnforcementTestsInput,\n): Promise<RunContractSummary> => {\n const endpoints = selectEndpoints(input);\n const skip = input.skip ?? [];\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n const metaMap = metaMapFor(endpoint, input.apiMetaMap, input.syncMetaMap);\n //? Only login-required, state-changing routes are CSRF-protected. A missing\n //? meta entry means we can't know the auth requirement — skip silently here\n //? (the auth layer already surfaces the stale-map gap).\n if (!hasMetaEntry(metaMap, endpoint)) continue;\n if (!requiresLogin(metaMap, endpoint)) continue;\n if (!STATE_CHANGING_METHODS.has(endpoint.method)) continue;\n\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n const result = await runCsrfEnforcementCheck({\n endpoint,\n baseUrl: input.baseUrl,\n authCookie: input.authCookie,\n inputFor: input.inputFor,\n requestTimeoutMs: input.requestTimeoutMs,\n expectedErrorCode: input.expectedErrorCode,\n expectedHttpStatus: input.expectedHttpStatus,\n transport: transportFor(endpoint, input.syncTransport),\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { defaultProbeTransport } from './probeRequest';\nimport type { ProbeTransport } from './probeRequest';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\n\n//? Lightweight fuzz layer — no Zod yet. Sends well-known junk shapes and\n//? asserts the server doesn't 5xx or return a non-envelope body. Real\n//? schema-driven fuzzing (TS type -> random valid input) is deferred until\n//? the generator emits Zod schemas alongside the types.\nconst JUNK_PAYLOADS: readonly unknown[] = [\n null,\n [],\n [1, 2, 3],\n 'string-instead-of-object',\n 1_234_567_890,\n true,\n { nested: { deeply: { nested: { value: 'x'.repeat(10_000) } } } },\n //? `{ __proto__: ... }` serializes to `{}` via JSON.stringify (prototype keys\n //? are not own-enumerable), so it would probe nothing. Use `constructor` instead\n //? — a named own-key that some parsers misinterpret but survives JSON round-trip.\n { constructor: { polluted: true } },\n { key: null, other: undefined, third: Number.NaN },\n];\n\nexport interface FuzzCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n headers?: Record<string, string>;\n /** Per-call request timeout in ms. Defaults to 5000. */\n requestTimeoutMs?: number;\n /** How the probes reach the server. Defaults to HTTP. */\n transport?: ProbeTransport;\n}\n\nexport const runFuzzCheck = async (input: FuzzCheckInput): Promise<ContractCheckResult> => {\n const started = Date.now();\n const requestTimeoutMs = input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const transport = input.transport ?? defaultProbeTransport;\n\n for (const payload of JUNK_PAYLOADS) {\n const result = await transport.send({\n endpoint: input.endpoint,\n baseUrl: input.baseUrl,\n body: payload,\n headers: input.headers,\n requestTimeoutMs,\n });\n const durationMs = Date.now() - started;\n\n if (!result) {\n return {\n endpoint: input.endpoint,\n status: 'fail',\n reason: `fuzz probe crashed (no response) with payload: ${JSON.stringify(payload).slice(0, 80)}`,\n durationMs,\n };\n }\n\n //? A 5xx that STILL returns a valid `{ status:'error', errorCode }` envelope\n //? is the framework gracefully catching an error (e.g. a route designed to\n //? throw), not a crash. The fuzz layer's concern is crashes / hangs /\n //? garbage — so we only fail on a non-envelope body below (which catches raw\n //? 5xx, HTML error pages, and truncated responses). A controlled 500\n //? envelope passes.\n if (!result.parsed || (result.parsed.status !== 'success' && result.parsed.status !== 'error')) {\n return {\n endpoint: input.endpoint,\n status: 'fail',\n httpStatus: result.httpStatus,\n reason: `fuzz payload produced non-envelope response: ${JSON.stringify(payload).slice(0, 80)}`,\n durationMs,\n };\n }\n\n if (result.parsed.status === 'error' && typeof result.parsed.errorCode !== 'string') {\n return {\n endpoint: input.endpoint,\n status: 'fail',\n httpStatus: result.httpStatus,\n reason: `fuzz error response missing errorCode for payload: ${JSON.stringify(payload).slice(0, 80)}`,\n durationMs,\n };\n }\n }\n\n return {\n endpoint: input.endpoint,\n status: 'pass',\n durationMs: Date.now() - started,\n };\n};\n","import { runFuzzCheck } from './fuzzCheck';\nimport { shouldSkip, calculateSummary, selectEndpoints, transportFor, STATE_CHANGING_METHODS } from './testLayerHelpers';\nimport type { ProbeTransport } from './probeRequest';\nimport type { ApiMethodMap, ContractCheckResult, RunContractSummary, SyncMetaMap } from './types';\n\nexport interface RunFuzzTestsInput {\n apiMethodMap: ApiMethodMap;\n /** Generated `syncMetaMap`. When given, sync routes are swept too. */\n syncMetaMap?: SyncMetaMap;\n baseUrl: string;\n skip?: string[];\n /** Substring filter on `<page>/<name>/<version>`; other routes are not requested at all. */\n filter?: string;\n headers?: Record<string, string>;\n /** Transport for sync routes (e.g. the socket transport). API routes always use HTTP. */\n syncTransport?: ProbeTransport;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runFuzzTests = async (input: RunFuzzTestsInput): Promise<RunContractSummary> => {\n const endpoints = selectEndpoints(input);\n const skip = input.skip ?? [];\n //? In cookie-mode (authenticated sweep) junk bodies sent to state-changing\n //? routes (POST/PUT/DELETE — every sync route) bypass the body-ignore\n //? short-circuit and reach real mutation handlers — risking unintended\n //? side-effects on the test database. Skip those endpoints here; the contract\n //? layer already validates their shape with well-formed input, and the CSRF\n //? layer probes their auth guard separately.\n const isAuthenticatedSweep = Boolean(input.headers?.Cookie);\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n if (isAuthenticatedSweep && STATE_CHANGING_METHODS.has(endpoint.method)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'state-changing route skipped in cookie-mode fuzz sweep (mutation risk with junk bodies)',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n const result = await runFuzzCheck({\n endpoint,\n baseUrl: input.baseUrl,\n headers: input.headers,\n transport: transportFor(endpoint, input.syncTransport),\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","//? Socket.io probe transport for sync routes. A project on the default\n//? `transport.invocation: 'socket'` sends every sync request over the socket,\n//? which runs a different server pipeline (`handleSyncRequest`) than the\n//? `/sync/*` HTTP route. Probing over HTTP alone would leave that pipeline —\n//? the one real users hit — without coverage.\n//?\n//? One socket per session identity (the probe's Cookie header, or none for an\n//? anonymous probe), opened lazily and reused for the whole sweep. An\n//? authenticated socket joins `receiver` once, so room-membership enforcement\n//? (`sync.requireRoomMembership`) sees a member, exactly like a real client that\n//? joined its room before syncing. API endpoints are delegated to HTTP — they\n//? have no socket transport in the sweep.\n\nimport type { Socket } from 'socket.io-client';\nimport { tryCatch } from '@luckystack/core';\n\nimport { connectTestSocket, emitSyncOverSocket, joinTestRoom, releaseTestSocket } from './testSocket';\nimport { createHttpProbeTransport } from './probeRequest';\nimport type { ProbeResponse, ProbeTransport, ProbeTransportRequest } from './probeRequest';\n\nconst LABEL = 'sync probe';\n\nexport interface SocketProbeTransportOptions {\n baseUrl: string;\n /** Room every sync probe targets; authenticated sockets join it first. */\n receiver: string;\n /** Session cookie name, used to read the token out of a probe's Cookie header. */\n sessionCookieName: string;\n}\n\n//? Pull `<token>` out of a `Cookie` header that carries `<cookieName>=<token>`.\nconst tokenFromCookieHeader = (cookieHeader: string | undefined, cookieName: string): string | null => {\n if (!cookieHeader) return null;\n for (const part of cookieHeader.split(';')) {\n const [rawName, ...rest] = part.trim().split('=');\n if (rawName === cookieName && rest.length > 0) return rest.join('=');\n }\n return null;\n};\n\nconst toProbeResponse = (payload: unknown): ProbeResponse => {\n if (typeof payload !== 'object' || payload === null) return { parsed: null };\n const record = payload as { status?: unknown; errorCode?: unknown };\n return {\n parsed: {\n status: typeof record.status === 'string' ? record.status : undefined,\n errorCode: typeof record.errorCode === 'string' ? record.errorCode : undefined,\n },\n };\n};\n\nexport const createSocketProbeTransport = (options: SocketProbeTransportOptions): ProbeTransport => {\n const http = createHttpProbeTransport({ syncReceiver: options.receiver });\n //? Keyed by token ('' = anonymous). A failed connect is cached as `null` so\n //? an unreachable socket endpoint fails each probe fast instead of retrying.\n const sockets = new Map<string, Promise<Socket | null>>();\n\n const socketFor = (token: string | null): Promise<Socket | null> => {\n const key = token ?? '';\n const existing = sockets.get(key);\n if (existing) return existing;\n const opening = (async (): Promise<Socket | null> => {\n const [connectError, socket] = await tryCatch(() => connectTestSocket({\n baseUrl: options.baseUrl,\n token,\n label: LABEL,\n sessionCookieName: options.sessionCookieName,\n }));\n if (connectError || !socket) return null;\n //? Anonymous sockets cannot join (the server answers `auth.required`);\n //? their probes then exercise exactly what an anonymous client gets.\n if (token) await tryCatch(() => joinTestRoom(socket, options.receiver, { label: LABEL }));\n return socket;\n })();\n sockets.set(key, opening);\n return opening;\n };\n\n const send = async (request: ProbeTransportRequest): Promise<ProbeResponse | null> => {\n if (request.endpoint.kind !== 'sync') return http.send(request);\n const socket = await socketFor(tokenFromCookieHeader(request.headers?.Cookie, options.sessionCookieName));\n if (!socket) return null;\n const outcome = await emitSyncOverSocket(socket, {\n routePath: `${request.endpoint.page}/${request.endpoint.name}/${request.endpoint.version}`,\n data: request.body,\n receiver: options.receiver,\n timeoutMs: request.requestTimeoutMs,\n });\n return outcome.kind === 'response' ? toProbeResponse(outcome.payload) : null;\n };\n\n const close = async (): Promise<void> => {\n const opened = [...sockets.entries()];\n sockets.clear();\n for (const [key, pending] of opened) {\n const socket = await pending;\n if (!socket) continue;\n //? An authenticated socket runs on the caller's session — release it so\n //? presence does not log that session out when the socket closes.\n if (key) await releaseTestSocket(socket);\n else socket.disconnect();\n }\n };\n\n return { name: 'socket', send, close };\n};\n","//? Socket.io client plumbing shared by every test-runner surface that speaks the\n//? socket transport: `ctx.watchStream` (observer socket), `ctx.callSync` in\n//? socket mode, and the sweep layers' socket probe transport. One place owns\n//? the handshake auth, the connect wait, the `joinRoom` ack and the `sync`\n//? request/response correlation, so the wire contract cannot drift between them.\n//?\n//? Wire contract (sourced from `@luckystack/sync` `syncRequest.ts`, the real\n//? client): emit `sync` with `{ name, data, cb, receiver, responseIndex,\n//? ignoreSelf }`; the server answers once on `sync-<responseIndex>`.\n\nimport { io as createSocketIoClient, Socket } from 'socket.io-client';\nimport {\n buildJoinRoomResponseEventName,\n buildSyncResponseEventName,\n getProjectConfig,\n socketEventNames,\n tryCatch,\n} from '@luckystack/core';\n\nconst DEFAULT_CONNECT_TIMEOUT_MS = 3000;\nconst DEFAULT_JOIN_TIMEOUT_MS = 3000;\n\n//? Monotonic per-process counter. Every request that needs a correlated reply\n//? (`joinRoom-<n>`, `sync-<n>`) takes a fresh index so concurrent sockets and\n//? sibling watchers in the same process never share a response event name.\nlet responseIndexCounter = 0;\nexport const nextResponseIndex = (): number => {\n responseIndexCounter += 1;\n return responseIndexCounter;\n};\n\nexport interface ConnectTestSocketInput {\n baseUrl: string;\n /** Session token, or null for an anonymous socket. */\n token: string | null;\n /** Prefix for error messages (e.g. `watchStream`, `callSync`). */\n label: string;\n /** Session cookie name. Defaults to the project's configured cookie. */\n sessionCookieName?: string;\n connectTimeoutMs?: number;\n}\n\nconst waitForConnect = (socket: Socket, label: string, timeoutMs: number): Promise<void> => {\n return new Promise<void>((resolve, reject) => {\n if (socket.connected) {\n resolve();\n return;\n }\n const timer = setTimeout(() => {\n cleanup();\n reject(new Error(`${label}: socket failed to connect within ${timeoutMs}ms`));\n }, timeoutMs);\n const onConnect = (): void => {\n cleanup();\n resolve();\n };\n const onConnectError = (err: Error): void => {\n cleanup();\n reject(new Error(`${label}: connect_error — ${err.message}`));\n };\n const cleanup = (): void => {\n clearTimeout(timer);\n socket.off('connect', onConnect);\n socket.off('connect_error', onConnectError);\n };\n socket.on('connect', onConnect);\n socket.on('connect_error', onConnectError);\n });\n};\n\n/**\n * Open a socket.io connection authenticated the way a browser would be, and\n * wait until it is connected. On a connect timeout / connect_error the socket\n * is torn down before the error is thrown, so a failed attempt never leaks.\n */\nexport const connectTestSocket = async (input: ConnectTestSocketInput): Promise<Socket> => {\n //? Authenticate in BOTH token modes. `auth.token` is the sessionBasedToken\n //? path; in the DEFAULT cookie mode the server reads the token ONLY from the\n //? session cookie (extractTokenFromSocket ignores handshake.auth.token unless\n //? `acceptBearerInCookieMode`), so without the Cookie header the socket would\n //? connect anonymously. In Node, socket.io-client honours `extraHeaders` on the\n //? websocket handshake, so the cookie is set there.\n const sessionCookieName = input.sessionCookieName ?? getProjectConfig().http.sessionCookieName;\n const socket: Socket = createSocketIoClient(input.baseUrl, {\n transports: ['websocket'],\n forceNew: true,\n reconnection: false,\n auth: input.token ? { token: input.token } : undefined,\n extraHeaders: input.token ? { Cookie: `${sessionCookieName}=${input.token}` } : undefined,\n });\n\n const [connectError] = await tryCatch(\n () => waitForConnect(socket, input.label, input.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS),\n );\n if (connectError) {\n socket.disconnect();\n throw connectError;\n }\n return socket;\n};\n\nconst RELEASE_TIMEOUT_MS = 1000;\n\n/**\n * Close a socket that runs on a session the runner does NOT own (the sweep's\n * `authToken`) without ending that session.\n *\n * With `@luckystack/presence` installed, a token whose last socket closes is\n * logged out once the short disconnect grace window expires — a closed browser\n * tab ends the session. A plain `disconnect()` therefore deleted the caller's\n * session seconds after every sweep. Sending `intentionalDisconnect` first is\n * the signal a real client sends on a tab switch: presence keeps the session\n * and the server closes the socket itself. Without presence nobody listens,\n * so the socket is closed locally after a short wait.\n */\nexport const releaseTestSocket = async (socket: Socket): Promise<void> => {\n if (!socket.connected) return;\n await new Promise<void>((resolve) => {\n const timer = setTimeout(() => {\n socket.off('disconnect', onDisconnect);\n resolve();\n }, RELEASE_TIMEOUT_MS);\n const onDisconnect = (): void => {\n clearTimeout(timer);\n resolve();\n };\n socket.once('disconnect', onDisconnect);\n socket.emit(socketEventNames.intentionalDisconnect);\n });\n socket.disconnect();\n};\n\n/**\n * Join `room` and wait for the server's ack. Rejects when the server refuses\n * the join (e.g. `auth.required` for an anonymous socket) or never answers.\n */\nexport const joinTestRoom = (\n socket: Socket,\n room: string,\n options: { label: string; timeoutMs?: number },\n): Promise<void> => {\n const timeoutMs = options.timeoutMs ?? DEFAULT_JOIN_TIMEOUT_MS;\n const responseIndex = nextResponseIndex();\n const eventName = buildJoinRoomResponseEventName(responseIndex);\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n socket.off(eventName, onAck);\n reject(new Error(`${options.label}: joinRoom ack timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n const onAck = (payload: unknown): void => {\n clearTimeout(timer);\n socket.off(eventName, onAck);\n if (typeof payload === 'object' && payload !== null && (payload as { status?: unknown }).status === 'error') {\n const errorCode = (payload as { errorCode?: unknown }).errorCode;\n reject(new Error(`${options.label}: joinRoom rejected — ${typeof errorCode === 'string' ? errorCode : 'unknown'}`));\n return;\n }\n resolve();\n };\n socket.on(eventName, onAck);\n socket.emit(socketEventNames.joinRoom, { group: room, responseIndex });\n });\n};\n\nexport interface EmitSyncInput {\n /** `<page>/<name>/<version>` — the route path without the `sync/` prefix. */\n routePath: string;\n data: unknown;\n receiver: string | undefined;\n ignoreSelf?: boolean;\n timeoutMs: number;\n}\n\nexport type EmitSyncOutcome =\n | { kind: 'response'; payload: unknown }\n | { kind: 'timeout' };\n\n/**\n * Send one sync request over `socket` exactly like the real client does and\n * resolve with the server's single reply on `sync-<responseIndex>`.\n */\nexport const emitSyncOverSocket = (socket: Socket, input: EmitSyncInput): Promise<EmitSyncOutcome> => {\n const responseIndex = nextResponseIndex();\n const eventName = buildSyncResponseEventName(responseIndex);\n return new Promise<EmitSyncOutcome>((resolve) => {\n const timer = setTimeout(() => {\n socket.off(eventName, onResponse);\n resolve({ kind: 'timeout' });\n }, input.timeoutMs);\n const onResponse = (payload: unknown): void => {\n clearTimeout(timer);\n socket.off(eventName, onResponse);\n resolve({ kind: 'response', payload });\n };\n socket.on(eventName, onResponse);\n socket.emit(socketEventNames.sync, {\n name: `sync/${input.routePath}`,\n //? Only `undefined` defaults — a fuzz probe's `null` must reach the server as-is.\n data: input.data === undefined ? {} : input.data,\n //? Same fallback cancel key the real client sends before the server's\n //? `__cancelId` handshake arrives; the runner never cancels, so it is\n //? only there to keep the request shape identical.\n cb: input.routePath,\n receiver: input.receiver,\n responseIndex,\n ignoreSelf: input.ignoreSelf ?? false,\n });\n });\n};\n","import type { z } from 'zod';\n\n//? Options threaded through the whole walk so nested string fields get the\n//? same treatment as top-level ones.\nexport interface SampleSchemaOptions {\n /**\n * Marker prefix applied to generated UNCONSTRAINED string values so any rows\n * a sweep creates are identifiable + collision-free (finding #98). Strings\n * that carry a format (email/url/uuid/datetime/…) or a check (min/max/\n * length/regex) are left as-is, since prefixing could break the route's own\n * validation and defeat the contract layer's \"valid input\" guarantee.\n */\n stringPrefix?: string;\n}\n\n//? Walk a Zod schema and produce a minimal valid value. Used by the\n//? contract/fuzz runners so \"valid input\" tests don't require hand-written\n//? fixtures per endpoint. Handles the shapes the generator emits\n//? (primitives, literals, unions, optionals, objects, records, arrays).\n//?\n//? Not a property-based generator — returns the SAME deterministic value\n//? every call. For randomized fuzz, swap to `fast-check` in a later session.\n\n//? zod 4 renamed the internal def accessor from `._def` to `.def`; recurse\n//? through whichever a child node exposes so the walker handles both versions.\nconst innerDef = (node: { _def?: unknown; def?: unknown } | undefined): unknown =>\n node ? (node.def ?? node._def) : undefined;\n\nconst sampleForDef = (def: unknown, options: SampleSchemaOptions | undefined): unknown => {\n //? Zod stores its runtime shape on `_def.type` (v3) or `_def.typeName`.\n //? Access is untyped because `def` can be any of dozens of internal shapes.\n const d = def as {\n typeName?: string;\n type?: string;\n innerType?: { _def?: unknown; def?: unknown };\n values?: unknown[] | Record<string, unknown>;\n entries?: Record<string, unknown>;\n value?: unknown;\n options?: { _def?: unknown; def?: unknown }[];\n shape?: (() => Record<string, { _def?: unknown; def?: unknown }>) | Record<string, { _def?: unknown; def?: unknown }>;\n valueType?: { _def: unknown };\n element?: { _def: unknown };\n items?: { _def?: unknown; def?: unknown }[];\n schema?: { _def?: unknown; def?: unknown };\n in?: { _def?: unknown; def?: unknown };\n //? String-constraint markers. zod 4 sets `format` + `check: 'string_format'`\n //? for email/url/uuid/datetime/cuid/… and a `checks` array for min/max/\n //? length/regex; zod 3 puts the lot on `checks`. A bare string has none.\n format?: string;\n check?: string;\n checks?: unknown[];\n };\n\n const name = d.typeName ?? d.type;\n\n switch (name) {\n case 'ZodString':\n case 'string': {\n //? Prefix only UNCONSTRAINED strings with the caller's test-data marker\n //? (finding #98). A format (email/url/uuid/datetime/…) or any check\n //? (min/max/length/regex) means a prefixed/lengthened value could fail the\n //? route's own Zod, turning the happy-path probe into a validation-error\n //? test — so those keep the plain `'test'`.\n const isConstrained = Boolean(d.format)\n || d.check === 'string_format'\n || (Array.isArray(d.checks) && d.checks.length > 0);\n if (options?.stringPrefix && !isConstrained) return `${options.stringPrefix}test`;\n return 'test';\n }\n case 'ZodNumber':\n case 'number': {\n return 0;\n }\n case 'ZodBoolean':\n case 'boolean': {\n return false;\n }\n case 'ZodNull':\n case 'null': {\n return null;\n }\n case 'ZodUndefined':\n case 'undefined': {\n return undefined;\n }\n case 'ZodAny':\n case 'any':\n case 'ZodUnknown':\n case 'unknown': {\n return null;\n }\n case 'ZodLiteral':\n case 'literal': {\n //? zod 3 stores the literal on `_def.value`; zod 4 stores `_def.values` (array).\n return d.value ?? (Array.isArray(d.values) ? d.values[0] : undefined);\n }\n case 'ZodOptional':\n case 'optional':\n case 'ZodNullable':\n case 'nullable': {\n //? Omitting optional fields is the most conservative valid input.\n return undefined;\n }\n case 'ZodEnum':\n case 'enum':\n case 'ZodNativeEnum':\n case 'nativeEnum': {\n //? Return the first declared value so an enum-typed required field gets a\n //? VALID member rather than `null` (which the route's own Zod rejects,\n //? silently turning the contract probe into a validation-error test).\n //? zod 3 stores members on `_def.values` (array); zod 4 on `_def.entries`\n //? (object) or `_def.values` (Set/array).\n if (Array.isArray(d.values)) return d.values[0];\n const entries = d.entries ?? (d.values && !Array.isArray(d.values) ? d.values : undefined);\n if (entries) {\n const first = Object.values(entries)[0];\n if (first !== undefined) return first;\n }\n return undefined;\n }\n case 'ZodDefault':\n case 'default':\n case 'ZodCatch':\n case 'catch': {\n //? Unwrap to the inner schema's sample (the field is still required at the\n //? type level; the default only applies when omitted).\n return sampleForDef(innerDef(d.innerType), options);\n }\n case 'ZodEffects':\n case 'effects':\n case 'ZodPipeline':\n case 'pipe':\n case 'ZodBranded':\n case 'branded':\n case 'ZodReadonly':\n case 'readonly': {\n //? `.refine`/`.transform`/`.pipe`/`.brand`/`.readonly` wrap an inner\n //? schema — recurse into it. A `.refine` may still reject the sample, but\n //? recursing yields a structurally-valid base far more often than `null`.\n return sampleForDef(innerDef(d.schema ?? d.in ?? d.innerType), options);\n }\n case 'ZodTuple':\n case 'tuple': {\n const items = d.items ?? [];\n return items.map(item => sampleForDef(innerDef(item), options));\n }\n case 'ZodUnion':\n case 'union':\n case 'ZodDiscriminatedUnion':\n case 'discriminatedUnion': {\n return d.options && d.options.length > 0 && d.options[0] ? sampleForDef(innerDef(d.options[0]), options) : undefined;\n }\n case 'ZodArray':\n case 'array': {\n return [];\n }\n case 'ZodObject':\n case 'object': {\n const out: Record<string, unknown> = {};\n //? zod 3 exposes `_def.shape` as a function; zod 4 as a plain object.\n const shape = typeof d.shape === 'function' ? d.shape() : (d.shape ?? {});\n for (const [key, valueSchema] of Object.entries(shape)) {\n const sample = sampleForDef(innerDef(valueSchema), options);\n if (sample !== undefined) out[key] = sample;\n }\n return out;\n }\n case 'ZodRecord':\n case 'record': {\n return {};\n }\n case 'ZodDate':\n case 'date': {\n return new Date().toISOString();\n }\n default: {\n //? Unknown node: OMIT (undefined) rather than emit `null`. A `null` for a\n //? required field of an unhandled type makes the route's own Zod reject\n //? the body, silently turning the \"happy path\" contract probe into a\n //? validation-error test. Omitting lets the object walker drop the key so\n //? the probe only fails if the field was genuinely required (a real,\n //? visible signal) instead of always.\n return undefined;\n }\n }\n};\n\nexport const sampleSchemaInput = (schema: z.ZodType, options?: SampleSchemaOptions): unknown => {\n //? zod 4 renamed the public def accessor from `._def` to `.def`; fall back to\n //? `._def` (zod 3) so the top-level entry handles both, matching the\n //? recursive `innerDef` helper.\n const node = schema as { def?: unknown; _def?: unknown };\n return sampleForDef(node.def ?? node._def, options);\n};\n","//? Builds the per-endpoint sample payload the sweep layers and registered\n//? custom layers send: a Zod-valid value generated from the route's runtime\n//? input schema (`apiInputSchemas` for API routes, `syncInputSchemas` for sync\n//? routes — both emitted by devkit), or `{}` when the route has no schema.\n\nimport type { ZodType } from 'zod';\n\nimport { sampleSchemaInput } from './schemaSampleInput';\nimport type { EndpointDescriptor } from './types';\n\nexport type InputSchemaMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, ZodType | undefined>>>>>>;\n\nexport interface CreateSampleInputForOptions {\n apiInputSchemas?: InputSchemaMap;\n syncInputSchemas?: InputSchemaMap;\n /**\n * Prefix for generated unconstrained strings — pass the run's\n * `createTestDataMarker()` so rows the probes create are identifiable.\n */\n stringPrefix?: string;\n}\n\nexport const createSampleInputFor = (options: CreateSampleInputForOptions) =>\n (endpoint: EndpointDescriptor): unknown => {\n const schemas = endpoint.kind === 'sync' ? options.syncInputSchemas : options.apiInputSchemas;\n const schema = schemas?.[endpoint.page]?.[endpoint.name]?.[endpoint.version];\n return schema ? sampleSchemaInput(schema, { stringPrefix: options.stringPrefix }) : {};\n };\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nconst isProcessRunning = (pid: unknown): boolean => {\n if (typeof pid !== 'number' || !Number.isInteger(pid) || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return error instanceof Error && 'code' in error && error.code === 'EPERM';\n }\n};\n\nexport interface ResolveTestBaseUrlOptions {\n /** Project root containing node_modules/.luckystack/dev-server.json. */\n cwd?: string;\n /** Used when no explicit override or valid live advertisement exists. */\n fallbackUrl?: string;\n}\n\n//? Shared by the framework repo and generated consumers so a dev backend that\n//? auto-increments off its configured port does not turn connection failures\n//? against the stale port into false test failures.\nexport const resolveTestBaseUrl = (\n options: ResolveTestBaseUrlOptions = {},\n): string => {\n const explicit = process.env.TEST_BASE_URL;\n if (explicit) return explicit;\n\n try {\n const raw = fs.readFileSync(\n path.join(options.cwd ?? process.cwd(), 'node_modules', '.luckystack', 'dev-server.json'),\n 'utf8',\n );\n const info = JSON.parse(raw) as { port?: unknown; pid?: unknown };\n if (\n typeof info.port === 'number'\n && Number.isInteger(info.port)\n && info.port > 0\n && info.port <= 65_535\n && isProcessRunning(info.pid)\n ) {\n return `http://localhost:${String(info.port)}`;\n }\n } catch {\n //? No live dev advertisement — use the caller's config-derived fallback.\n }\n\n return options.fallbackUrl ?? 'http://localhost:80';\n};\n","//? @adr 0032 — test-process env/secret bootstrap through lazy consumer config.\n//? Test-process bootstrap for environment-backed integrations. The live server\n//? resolves secret-manager pointers in its own process, but Layer-5 tests also\n//? use Prisma/Redis directly inside the runner process. That process therefore\n//? needs the same env prefix BEFORE custom test modules are imported.\n\nimport { loadEnvFiles, tryCatch } from '@luckystack/core';\n\ntype MaybePromise<T> = T | Promise<T>;\n\ninterface SecretManagerConfigLike {\n url: string;\n token: string | { fromFile: string };\n [key: string]: unknown;\n}\n\nexport interface ResolveTestEnvironmentInput {\n /**\n * Loads the consumer's default-exported `config.ts` object after `.env` files\n * are available. Keeping this as a callback avoids hardcoding a consumer path\n * inside the package and keeps `@luckystack/secret-manager` optional.\n */\n loadProjectConfig?: () => MaybePromise<unknown>;\n}\n\ninterface RequiredProjectConfigLoader {\n loadProjectConfig: NonNullable<ResolveTestEnvironmentInput['loadProjectConfig']>;\n}\n\n/** @internal Runtime guard for untyped callers of the public orchestrators. */\nexport const assertProjectConfigLoader: (\n input: unknown,\n caller: 'runAllTests' | 'runCustomTests',\n) => asserts input is RequiredProjectConfigLoader = (input, caller) => {\n if (\n typeof input !== 'object'\n || input === null\n || !('loadProjectConfig' in input)\n || typeof input.loadProjectConfig !== 'function'\n ) {\n throw new TypeError(`[test-runner] ${caller} requires a lazy loadProjectConfig callback`);\n }\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null;\n\nconst isToken = (value: unknown): value is SecretManagerConfigLike['token'] =>\n typeof value === 'string'\n || (isRecord(value) && typeof value.fromFile === 'string');\n\nconst readSecretManagerConfig = (projectConfig: unknown): SecretManagerConfigLike | null => {\n if (!isRecord(projectConfig)) return null;\n const candidate = projectConfig.secretManager;\n if (!isRecord(candidate)) return null;\n\n const url = candidate.url;\n if (url === undefined || url === '') return null;\n if (typeof url !== 'string') {\n throw new TypeError('[test-runner] config.secretManager.url must be a string.');\n }\n if (!isToken(candidate.token)) {\n throw new TypeError(\n '[test-runner] config.secretManager.token must be a string or { fromFile: string }.',\n );\n }\n\n return { ...candidate, url, token: candidate.token };\n};\n\n/**\n * Load the project's env files and resolve optional secret-manager pointers for\n * the TEST process. Call before importing integration-test modules that touch a\n * database, Redis, or another env-backed SDK.\n */\nexport const resolveTestEnvironment = async (\n input: ResolveTestEnvironmentInput = {},\n): Promise<void> => {\n loadEnvFiles();\n if (!input.loadProjectConfig) return;\n\n const projectConfig = await input.loadProjectConfig();\n const secretManagerConfig = readSecretManagerConfig(projectConfig);\n if (!secretManagerConfig) return;\n\n const [importError, secretManager] = await tryCatch(\n () => import('@luckystack/secret-manager'),\n );\n if (importError || !secretManager) {\n throw new Error(\n '[test-runner] config.secretManager.url is set, but @luckystack/secret-manager '\n + 'could not be loaded. Install the package or remove the secret-manager config; '\n + 'tests will not continue with unresolved env pointers.',\n { cause: importError ?? undefined },\n );\n }\n\n await secretManager.initSecretManager({\n ...secretManagerConfig,\n //? Match the normal server/ORM bootstrap: configured remote resolution is\n //? fail-fast. A raw DATABASE_URL_V<n> must never leak into Prisma.\n source: 'remote',\n });\n};\n","import { randomUUID } from 'node:crypto';\n\n//? Contract-test mutation safety (finding #98). The contract sweep posts\n//? generated sample payloads at every endpoint, which can create real rows.\n//? Tagging generated string values with a recognizable, run-unique marker\n//? makes any such row identifiable (so the reset bookends — and a human\n//? scanning the DB — can tell test data from real data) and collision-free\n//? (no chance a generated value matches an existing real record's field).\n\n//? Fixed leading token every generated test string starts with. Stable across\n//? runs so consumers / DB scans can match `lstest_*` to spot stray test data.\nexport const TEST_DATA_PREFIX = 'lstest_';\n\n//? Build a run-unique marker like `lstest_a1b2c3d4_`. The short uuid segment\n//? keeps two concurrent runs from generating colliding values while staying\n//? short enough not to blow past typical string length limits.\nexport const createTestDataMarker = (): string => `${TEST_DATA_PREFIX}${randomUUID().slice(0, 8)}_`;\n","import type { ContractCheckResult, RunContractSummary } from './types';\n\nconst statusIcon = (status: ContractCheckResult['status']): string => {\n switch (status) {\n case 'pass': { return '[PASS]';\n }\n case 'fail': { return '[FAIL]';\n }\n case 'skipped': { return '[SKIP]';\n }\n }\n};\n\nexport const logContractResult = (result: ContractCheckResult): void => {\n const { endpoint, durationMs, httpStatus, responseStatus, errorCode, reason } = result;\n const icon = statusIcon(result.status);\n const core = `${icon} ${endpoint.method} /${endpoint.fullPath} ${durationMs}ms`;\n\n if (result.status === 'pass') {\n const tail = responseStatus === 'error' ? ` (error: ${errorCode ?? 'unknown'})` : '';\n console.log(`${core} http=${httpStatus ?? '-'}${tail}`);\n return;\n }\n\n if (result.status === 'skipped') {\n console.log(`${core} ${reason ?? ''}`);\n return;\n }\n\n console.log(`${core} http=${httpStatus ?? '-'} reason=${reason ?? '(none)'}`);\n};\n\nexport const logContractSummary = (summary: RunContractSummary): void => {\n const { total, passed, failed, skipped } = summary;\n console.log('');\n console.log(`Contract tests: ${passed}/${total} passed, ${failed} failed, ${skipped} skipped`);\n\n if (failed > 0) {\n console.log('');\n console.log('Failures:');\n for (const result of summary.results) {\n if (result.status !== 'fail') continue;\n console.log(` - /${result.endpoint.fullPath}: ${result.reason ?? '(no reason)'}`);\n }\n }\n};\n","//? Per-route business-logic test layer. Discovers `*.tests.ts` files\n//? alongside `_api/` and `_sync/` route source, dynamic-imports each,\n//? builds a route-bound `TestContext`, runs the exported `customTests`\n//? cases. Spec: docs/ARCHITECTURE_TESTING.md.\n\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport {\n buildRoutedGetUrl,\n getCsrfConfig,\n getPrismaClient,\n getProjectConfig,\n getSrcDir,\n tryCatch,\n tryCatchSync,\n} from '@luckystack/core';\n\nimport {\n assertProjectConfigLoader,\n resolveTestEnvironment,\n type ResolveTestEnvironmentInput,\n} from './resolveTestEnvironment';\nimport type { Socket } from 'socket.io-client';\n\nimport {\n openStreamWatcher,\n type StreamChunkFrame,\n type StreamWatcher,\n} from './streamWatcher';\nimport { connectTestSocket, emitSyncOverSocket, joinTestRoom } from './testSocket';\n//? `ApiMethodMap` mirrors the generated `apiMethodMap` shape (page → name →\n//? version → method) and is a type-only import, so there's still no import-time\n//? coupling to the consumer's generated artifact.\nimport type { ApiMethodMap } from './types';\n\nconst API_TEST_FILE_PATTERN = /_v(\\d+)\\.tests\\.ts$/;\nconst SYNC_SERVER_TEST_FILE_PATTERN = /_server_v(\\d+)\\.tests\\.ts$/;\n\n//? Default per-request timeout for the Layer-5 call helpers. Mirrors the sweep\n//? layers so a hung (non-erroring) server can't wedge the whole custom-test run\n//? indefinitely — without it the case's own tryCatch never fires.\nconst DEFAULT_CALL_TIMEOUT_MS = 10_000;\n\nexport interface CustomTestCase {\n name: string;\n run: (ctx: TestContext) => Promise<void>;\n /**\n * Mark this case as a KNOWN, accepted failure (e.g. a documented bug not\n * yet fixed, or a scenario the current implementation cannot satisfy). The\n * string is the reason shown in the report. A case marked `expectedToFail`\n * that throws is reported as `xfail` — counted separately, NOT a red\n * failure. If it unexpectedly PASSES it is reported as `xpass` so the stale\n * marker can be removed. Leave undefined for normal cases.\n */\n expectedToFail?: string;\n}\n\nexport interface CustomTestResult {\n routePath: string;\n caseName: string;\n //? `xfail` = marked expectedToFail and failed as expected (known issue).\n //? `xpass` = marked expectedToFail but passed (remove the marker).\n status: 'pass' | 'fail' | 'xfail' | 'xpass';\n durationMs: number;\n reason?: string;\n /** Documented reason when the case carried an `expectedToFail` marker. */\n expectedToFail?: string;\n}\n\nexport interface RunCustomTestsSummary {\n total: number;\n passed: number;\n failed: number;\n /** Cases marked `expectedToFail` that failed as expected (known issues). */\n xfailed: number;\n /** Cases marked `expectedToFail` that unexpectedly passed (remove the marker). */\n xpassed: number;\n results: CustomTestResult[];\n}\n\nexport interface RunCustomTestsInput {\n baseUrl: string;\n /** Optional cookie name for the session token. Defaults to the project's configured cookie. */\n sessionCookieName?: string;\n /** Filter to a subset of routes — substring match against `<page>/<name>/<version>`. */\n filter?: string;\n /**\n * Generated `apiMethodMap` so the harness sends each API route its declared\n * HTTP method (e.g. logout is DELETE). Falls back to POST when absent.\n */\n apiMethodMap?: ApiMethodMap;\n /**\n * Lazy consumer config for direct Layer-5 runs. Required so the public path\n * cannot silently skip a configured secret manager; `runAllTests` prepares\n * the environment separately before using the internal Layer-5 entrypoint.\n */\n loadProjectConfig: NonNullable<ResolveTestEnvironmentInput['loadProjectConfig']>;\n onResult?: (result: CustomTestResult) => void;\n}\n\ntype PreparedRunCustomTestsInput = Omit<RunCustomTestsInput, 'loadProjectConfig'>;\n\ninterface DiscoveredTestFile {\n /** Absolute path to the test file. */\n filePath: string;\n /** `api` or `sync` — which transport this route uses. */\n kind: 'api' | 'sync';\n /** Route descriptor parsed from the filename + directory location. */\n route: { page: string; name: string; version: string };\n}\n\n//? Walk `src/` recursively. A file matches when its directory contains an\n//? `_api/` or `_sync/` segment AND the filename matches the version suffix\n//? pattern. The route's `page` is everything between `src/` and the marker\n//? segment, joined with `/`. Nested page folders are supported (e.g.\n//? `src/admin/users/_api/getUser_v1.tests.ts` → page `admin/users`).\nexport const discoverCustomTestFiles = (srcDir: string = getSrcDir()): DiscoveredTestFile[] => {\n if (!fs.existsSync(srcDir)) return [];\n const found: DiscoveredTestFile[] = [];\n const stack: string[] = [srcDir];\n //? Track visited real-paths so a cyclic symlink under `src/` can't drive an\n //? infinite walk / unbounded stack growth.\n const visited = new Set<string>();\n while (stack.length > 0) {\n const current = stack.pop();\n if (current === undefined) break;\n //? Resolve to the canonical path and skip if already seen (symlink cycle).\n const [realError, realPath] = tryCatchSync(() => fs.realpathSync(current));\n const canonical = realError || realPath === null ? current : realPath;\n if (visited.has(canonical)) continue;\n visited.add(canonical);\n //? Guard the readdir: a permission error or a directory deleted mid-walk\n //? throws synchronously and would otherwise abort the whole custom layer.\n //? Skip the unreadable directory instead.\n const [readError, entries] = tryCatchSync(() => fs.readdirSync(current, { withFileTypes: true }));\n if (readError || entries === null) continue;\n for (const entry of entries) {\n const fullPath = path.join(current, entry.name);\n //? Skip symlinked entries entirely — following them risks a cycle and\n //? a discovered test file should live in the real source tree.\n if (entry.isSymbolicLink()) continue;\n if (entry.isDirectory()) {\n stack.push(fullPath);\n continue;\n }\n if (!entry.isFile() || !entry.name.endsWith('.tests.ts')) continue;\n const parentDir = path.basename(current);\n if (parentDir !== '_api' && parentDir !== '_sync') continue;\n const pattern = parentDir === '_api' ? API_TEST_FILE_PATTERN : SYNC_SERVER_TEST_FILE_PATTERN;\n const versionMatch = pattern.exec(entry.name);\n if (!versionMatch) continue;\n const version = `v${versionMatch[1]}`;\n const namePart = entry.name.replace(parentDir === '_api' ? /_v\\d+\\.tests\\.ts$/ : /_server_v\\d+\\.tests\\.ts$/, '');\n const pageDir = path.dirname(current);\n //? Top-level `src/_api/` (no page folder) routes under the framework's\n //? `system` page — mirror that, otherwise an empty page produces the\n //? malformed URL `/api//logout/v1` and the call 404s.\n const relativePage = path.relative(srcDir, pageDir).replaceAll('\\\\', '/') || 'system';\n found.push({\n filePath: fullPath,\n kind: parentDir === '_api' ? 'api' : 'sync',\n route: { page: relativePage, name: namePart, version },\n });\n }\n }\n return found.toSorted((a, b) => a.filePath.localeCompare(b.filePath));\n};\n\nexport interface CallSyncOptions {\n /** Room the sync targets (the server requires one). */\n receiver?: string;\n /** Skip the sender's own socket in the fan-out (socket transport). */\n ignoreSelf?: boolean;\n /** Force a transport. Defaults to the project's `transport.invocation`. */\n transport?: 'socket' | 'http';\n /** Socket transport: join `receiver` before sending. Default `true`. */\n joinRoom?: boolean;\n}\n\n//? Test fixture shapes. The TestContext is built per route — each per-route\n//? test file's `customTests` cases receive a context already bound to the\n//? route the file lives next to, so authors don't repeat `page/name/version`.\nexport interface TestContext {\n /**\n * Invoke the route under test. Page / name / version are baked in.\n *\n * `input` travels exactly like the real client sends it: as the JSON body\n * for POST / PUT / DELETE routes, and as the `__luckystack_data` query-string\n * value for GET routes (a GET cannot carry a body). Either way the server\n * validates it as the route's `data`, so a GET route with required fields\n * receives them.\n */\n //? Two type parameters by design — `TInput` lets callers document the\n //? typed shape they're sending even though we don't validate against it\n //? here (the route's own runtime validation does). The ESLint rule\n //? otherwise flags it as \"used once\"; the documentation value is real.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- callsite-documentation use\n callApi: <TInput = unknown, TOutput = unknown>(input: TInput) => Promise<TOutput>;\n /**\n * Invoke the sync route under test over the SAME transport the project's\n * real client uses (`transport.invocation`): Socket.io for `'socket'` (the\n * default), the `/sync/*` HTTP route for `'routed-http'`. Override per call\n * with `opts.transport`. Over the socket, the case's session socket first\n * joins `opts.receiver` (like a real client joins its room before syncing);\n * pass `joinRoom: false` to test a non-member.\n */\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- callsite-documentation use\n callSync: <TInput = unknown, TOutput = unknown>(input: TInput, opts?: CallSyncOptions) => Promise<TOutput>;\n /**\n * Open a second socket connection that joins `roomCode` as a pure\n * subscriber and exposes the incoming chunk stream for assertions.\n *\n * The route is auto-derived from the test file location, so a watcher\n * only surfaces chunks for the route this test belongs to. Multiple\n * watchers per test are allowed; all are auto-closed when the test\n * case finishes (whether it passes or throws).\n */\n //? TChunk documents the expected chunk type at the call site.\n watchStream: <TChunk extends StreamChunkFrame = StreamChunkFrame>(roomCode: string) => Promise<StreamWatcher<TChunk>>;\n session: {\n login: (user?: { email?: string; id?: string; name?: string }) => Promise<{ token: string; userId: string }>;\n logout: () => Promise<void>;\n current: () => { token: string | null; userId: string | null };\n };\n prisma: ReturnType<typeof getPrismaClient>;\n expect: TestExpect;\n}\n\nexport interface TestExpect {\n eq: <T>(actual: T, expected: T, message?: string) => void;\n ok: (value: unknown, message?: string) => void;\n throws: (fn: () => Promise<unknown>, message?: string) => Promise<Error>;\n matches: (value: string, pattern: RegExp, message?: string) => void;\n}\n\nclass AssertionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AssertionError';\n }\n}\n\nconst buildExpect = (): TestExpect => ({\n eq: (actual, expected, message) => {\n const ok = Object.is(actual, expected)\n || (typeof actual === 'object' && typeof expected === 'object' && JSON.stringify(actual) === JSON.stringify(expected));\n if (!ok) {\n const prefix = message ? `${message}: ` : '';\n throw new AssertionError(`${prefix}expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`);\n }\n },\n ok: (value, message) => {\n if (!value) throw new AssertionError(message ?? `expected truthy value, got ${JSON.stringify(value)}`);\n },\n throws: async (fn, message) => {\n const [err] = await tryCatch(fn);\n if (!err) throw new AssertionError(message ?? 'expected fn to throw, but it did not');\n return err;\n },\n matches: (value, pattern, message) => {\n if (!pattern.test(value)) {\n const prefix = message ? `${message}: ` : '';\n throw new AssertionError(`${prefix}\"${value}\" does not match ${pattern}`);\n }\n },\n});\n\ninterface TestSessionState {\n token: string | null;\n userId: string | null;\n //? CSRF token minted by saveSession on login. Sent on authenticated\n //? state-changing requests so the server's CSRF middleware doesn't 403.\n csrfToken: string | null;\n //? Raw JSON body of the most recent callApi / callSync response. Surfaced in\n //? a failed case's reason so the report shows the server's actual errorCode\n //? (e.g. `api.rateLimitExceeded`) instead of only the assertion text.\n lastResponse: unknown;\n}\n\n//? Pull the server's errorCode (or non-success status) out of the last HTTP\n//? response so a failure's reason shows WHY the server rejected the call, not\n//? just the assertion text. Returns undefined for success / non-envelope bodies.\nconst extractErrorCode = (response: unknown): string | undefined => {\n if (!response || typeof response !== 'object') return undefined;\n const r = response as { errorCode?: unknown; status?: unknown };\n if (typeof r.errorCode === 'string' && r.errorCode.length > 0) return r.errorCode;\n if (typeof r.status === 'string' && r.status !== 'success') return r.status;\n return undefined;\n};\n\n//? Minimal session login: mints a session in Redis via the consumer's\n//? registered SessionAdapter (from `@luckystack/login`). Deliberately does\n//? NOT create the Prisma user record — User-table shape varies per\n//? project. If your test needs a real user row, create it via\n//? `ctx.prisma.user.create(...)` first, then pass `{ id, email }` to\n//? `session.login()`.\nconst buildSessionHelpers = (state: TestSessionState): TestContext['session'] => {\n return {\n login: async (user) => {\n const { saveSession, getSession } = await import('@luckystack/login');\n const userId = user?.id ?? `test-user-${Date.now()}-${crypto.randomBytes(8).toString('hex')}`;\n const email = user?.email ?? `test-${Date.now()}-${crypto.randomBytes(8).toString('hex')}@example.com`;\n const name = user?.name ?? 'Test User';\n const token = `test-${Date.now()}-${crypto.randomBytes(16).toString('hex')}`;\n const sessionData = {\n id: userId,\n email,\n name,\n token,\n provider: 'credentials' as const,\n };\n await saveSession(token, sessionData, true);\n state.token = token;\n state.userId = userId;\n //? Read back the CSRF token saveSession minted so authenticated\n //? state-changing requests can pass the server's CSRF middleware.\n const persisted = await getSession(token);\n state.csrfToken = persisted?.csrfToken ?? null;\n return { token, userId };\n },\n logout: async () => {\n if (!state.token) return;\n const { deleteSession } = await import('@luckystack/login');\n await deleteSession(state.token);\n state.token = null;\n state.userId = null;\n state.csrfToken = null;\n },\n current: () => ({ token: state.token, userId: state.userId }),\n };\n};\n\n//? Parse a fetch Response as JSON behind tryCatch and store the raw body on the\n//? session state. A non-JSON body (HTML 500, empty 204, truncated) would\n//? otherwise throw a raw SyntaxError with `state.lastResponse` null, hiding the\n//? server's actual output. On parse failure we keep a snippet of the text body\n//? so a failed case's reason can show what the server actually returned.\nconst parseResponse = async (response: Response, state: TestSessionState): Promise<unknown> => {\n const [textError, text] = await tryCatch(() => response.text());\n if (textError || text === null) {\n state.lastResponse = null;\n throw new Error(`failed to read response body (HTTP ${response.status}): ${textError?.message ?? 'unknown'}`);\n }\n const [parseError, parsed] = tryCatchSync(() => JSON.parse(text) as unknown);\n if (parseError) {\n const snippet = text.length > 500 ? `${text.slice(0, 500)}…` : text;\n state.lastResponse = snippet;\n throw new Error(`response was not valid JSON (HTTP ${response.status}): ${snippet}`);\n }\n state.lastResponse = parsed;\n return parsed;\n};\n\n/** @internal Exported for the unit test only; consumers use `TestContext.callApi`. */\nexport const buildCallApi = (\n baseUrl: string,\n routePath: string,\n state: TestSessionState,\n cookieName: string,\n //? The route's declared HTTP method (from the generated apiMethodMap). The\n //? server validates the method, so DELETE/PUT routes (e.g. logout) 405 if we\n //? always sent POST.\n method: string,\n): TestContext['callApi'] => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- must match TestContext['callApi'] generic signature\n return async <TInput = unknown, TOutput = unknown>(input: TInput): Promise<TOutput> => {\n const path = `${baseUrl.replace(/\\/$/, '')}/api/${routePath}`;\n const headers: Record<string, string> = { 'Content-Type': 'application/json', Origin: new URL(baseUrl).origin };\n if (state.token) headers.Cookie = `${cookieName}=${state.token}`;\n if (state.csrfToken) headers[getCsrfConfig().headerName] = state.csrfToken;\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, DEFAULT_CALL_TIMEOUT_MS);\n const init: RequestInit = { method, headers, signal: controller.signal };\n //? GET / HEAD requests cannot carry a body (fetch throws), so the payload\n //? rides the query string under the same reserved key the real client\n //? uses and the server reads. Every other method sends the JSON body.\n const bodyless = method === 'GET' || method === 'HEAD';\n const url = bodyless ? buildRoutedGetUrl(path, input ?? {}) : path;\n if (!bodyless) init.body = JSON.stringify(input ?? {});\n const [fetchError, response] = await tryCatch(() => fetch(url, init));\n clearTimeout(timeoutHandle);\n if (fetchError || !response) {\n state.lastResponse = null;\n throw new Error(`callApi ${method} ${routePath} failed: ${fetchError?.message ?? 'no response'}`);\n }\n //? `parseResponse` returns `unknown`; TOutput is the caller-supplied expectation.\n //? The runtime shape is unverifiable here — the route's own validator owns that\n //? contract. The cast is required because parseResponse must return `unknown`.\n return await parseResponse(response, state) as TOutput;\n };\n};\n\n//? The case's sync socket. Opened lazily on the first socket `callSync`, reopened\n//? when the case's session changes (a `session.login()` after the first call),\n//? and closed with the case's watchers. Tracks joined rooms so repeated calls to\n//? the same receiver join once.\ninterface CaseSyncSocket {\n get: (token: string | null) => Promise<{ socket: Socket; joinedRooms: Set<string> }>;\n close: () => void;\n}\n\nconst createCaseSyncSocket = (baseUrl: string, cookieName: string): CaseSyncSocket => {\n let current: { socket: Socket; token: string | null; joinedRooms: Set<string> } | null = null;\n const close = (): void => {\n current?.socket.disconnect();\n current = null;\n };\n return {\n get: async (token) => {\n if (current?.token === token && current.socket.connected) return current;\n close();\n const socket = await connectTestSocket({ baseUrl, token, label: 'callSync', sessionCookieName: cookieName });\n current = { socket, token, joinedRooms: new Set() };\n return current;\n },\n close,\n };\n};\n\nconst resolveSyncTransport = (opts: CallSyncOptions | undefined): 'socket' | 'http' =>\n opts?.transport ?? (getProjectConfig().transport.invocation === 'routed-http' ? 'http' : 'socket');\n\nconst buildCallSync = (\n baseUrl: string,\n routePath: string,\n state: TestSessionState,\n cookieName: string,\n syncSocket: CaseSyncSocket,\n): TestContext['callSync'] => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- must match TestContext['callSync'] generic signature\n return async <TInput = unknown, TOutput = unknown>(input: TInput, opts?: CallSyncOptions): Promise<TOutput> => {\n if (resolveSyncTransport(opts) === 'socket') {\n const { socket, joinedRooms } = await syncSocket.get(state.token);\n const { receiver, joinRoom = true } = opts ?? {};\n if (receiver && receiver !== 'all' && joinRoom && !joinedRooms.has(receiver)) {\n //? Best-effort: a refused join (e.g. anonymous socket) is not thrown —\n //? the sync itself then returns the server's real rejection envelope.\n const [joinError] = await tryCatch(() => joinTestRoom(socket, receiver, { label: 'callSync' }));\n if (!joinError) joinedRooms.add(receiver);\n }\n const outcome = await emitSyncOverSocket(socket, {\n routePath,\n data: input,\n receiver,\n ignoreSelf: opts?.ignoreSelf,\n timeoutMs: DEFAULT_CALL_TIMEOUT_MS,\n });\n if (outcome.kind === 'timeout') {\n state.lastResponse = null;\n throw new Error(`callSync ${routePath} failed: no socket response within ${DEFAULT_CALL_TIMEOUT_MS}ms`);\n }\n state.lastResponse = outcome.payload;\n //? The socket payload is unknown; TOutput is the caller's type contract, verified at the call site\n return outcome.payload as TOutput;\n }\n\n const url = `${baseUrl.replace(/\\/$/, '')}/sync/${routePath}`;\n const headers: Record<string, string> = { 'Content-Type': 'application/json', Origin: new URL(baseUrl).origin };\n if (state.token) headers.Cookie = `${cookieName}=${state.token}`;\n if (state.csrfToken) headers[getCsrfConfig().headerName] = state.csrfToken;\n const body = { data: input ?? {}, receiver: opts?.receiver, ignoreSelf: opts?.ignoreSelf };\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, DEFAULT_CALL_TIMEOUT_MS);\n const [fetchError, response] = await tryCatch(() => fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n signal: controller.signal,\n }));\n clearTimeout(timeoutHandle);\n if (fetchError || !response) {\n state.lastResponse = null;\n throw new Error(`callSync ${routePath} failed: ${fetchError?.message ?? 'no response'}`);\n }\n //? parseResponse returns unknown; TOutput is the caller's type contract, verified at the call site\n return await parseResponse(response, state) as TOutput;\n };\n};\n\ninterface BuiltContext {\n ctx: TestContext;\n /** Close every watcher and the sync socket opened during the case. Called by `runCustomTests` after each case. */\n closeAllWatchers: () => Promise<void>;\n /** Per-case session state — read after the run to surface the last response's errorCode. */\n state: TestSessionState;\n}\n\nconst buildContext = (\n discovery: DiscoveredTestFile,\n input: PreparedRunCustomTestsInput,\n): BuiltContext => {\n //? Default to the PROJECT's configured session cookie name (the server reads\n //? the token from this cookie). Hardcoding a name silently breaks every\n //? authenticated custom test when the project uses a different name.\n const cookieName = input.sessionCookieName ?? getProjectConfig().http.sessionCookieName;\n const routePath = `${discovery.route.page}/${discovery.route.name}/${discovery.route.version}`;\n const routeFullName = `sync/${routePath}`;\n //? API routes are invoked with their declared HTTP method; sync routes always\n //? POST. Default to POST when the map lacks the route (e.g. sync, or a map\n //? wasn't threaded through).\n const apiMethod = input.apiMethodMap?.[discovery.route.page]?.[discovery.route.name]?.[discovery.route.version] ?? 'POST';\n const state: TestSessionState = { token: null, userId: null, csrfToken: null, lastResponse: null };\n\n //? Watchers are tracked per case so a missing explicit `close()` can't\n //? leak socket B between tests.\n const watchers: StreamWatcher[] = [];\n const watchStream = async <TChunk extends StreamChunkFrame = StreamChunkFrame>(\n roomCode: string,\n ): Promise<StreamWatcher<TChunk>> => {\n const watcher = await openStreamWatcher<TChunk>({\n baseUrl: input.baseUrl,\n roomCode,\n token: state.token,\n routeFullName,\n });\n //? Track watchers under the base `StreamChunkFrame` shape; the\n //? returned reference keeps the caller-supplied generic.\n watchers.push(watcher);\n return watcher;\n };\n\n const syncSocket = createCaseSyncSocket(input.baseUrl, cookieName);\n\n const closeAllWatchers = async (): Promise<void> => {\n syncSocket.close();\n while (watchers.length > 0) {\n const watcher = watchers.pop();\n if (!watcher) continue;\n const [closeError] = await tryCatch(() => watcher.close());\n if (closeError) {\n //? Swallow — closing in cleanup must never mask the original test\n //? failure. Surface via logger if a project wires one up.\n //? (Intentionally not using getLogger here to avoid an extra import.)\n }\n }\n };\n\n const ctx: TestContext = {\n callApi: buildCallApi(input.baseUrl, routePath, state, cookieName, apiMethod),\n callSync: buildCallSync(input.baseUrl, routePath, state, cookieName, syncSocket),\n watchStream,\n session: buildSessionHelpers(state),\n //? Lazy: only resolves when a case actually touches `ctx.prisma`, so an\n //? orm:'none' project (no registered client) can still run DB-free custom\n //? tests — the eager resolve used to abort the whole custom-test phase.\n get prisma() {\n return getPrismaClient();\n },\n expect: buildExpect(),\n };\n\n return { ctx, closeAllWatchers, state };\n};\n\n/** @internal Environment is already prepared by `runAllTests`. */\nexport const runCustomTestsAfterEnvironmentPrepared = async (\n input: PreparedRunCustomTestsInput,\n): Promise<RunCustomTestsSummary> => {\n const srcDir = getSrcDir();\n const files = discoverCustomTestFiles(srcDir);\n const results: CustomTestResult[] = [];\n\n for (const discovery of files) {\n const routePath = `${discovery.route.page}/${discovery.route.name}/${discovery.route.version}`;\n if (input.filter && !routePath.includes(input.filter)) continue;\n\n //? Canonical-path containment guard: resolve to the real path and verify\n //? it is still inside srcDir. Protects against symlink-escaped paths or\n //? an adversarially crafted filePath that slipped through the walker.\n const [realPathErr, realFilePath] = tryCatchSync(() => fs.realpathSync(discovery.filePath));\n const resolvedPath = realPathErr ? discovery.filePath : (realFilePath ?? discovery.filePath);\n const relToSrc = path.relative(srcDir, resolvedPath);\n if (relToSrc.startsWith('..') || path.isAbsolute(relToSrc)) {\n const r: CustomTestResult = {\n routePath, caseName: '(import)', status: 'fail', durationMs: 0,\n reason: `test file path escapes srcDir and was rejected: ${discovery.filePath}`,\n };\n results.push(r);\n input.onResult?.(r);\n continue;\n }\n\n const [importError, mod] = await tryCatch(\n () => import(pathToFileURL(resolvedPath).href) as Promise<Record<string, unknown>>,\n );\n if (importError || !mod) {\n const r: CustomTestResult = {\n routePath, caseName: '(import)', status: 'fail', durationMs: 0,\n reason: `failed to import ${discovery.filePath}: ${importError?.message ?? 'unknown'}`,\n };\n results.push(r);\n input.onResult?.(r);\n continue;\n }\n\n const exported = (mod as { customTests?: unknown }).customTests;\n //? DD-TR-various — empty-stub contract (documented decision):\n //? A test file that exports an empty array (or no `customTests` export) is\n //? silently skipped, NOT emitted as a `skipped` result. Rationale: AI-generated\n //? stub files start life empty; surfacing them as \"skipped\" every run adds noise\n //? before the author fills them in. The `CustomTestResult` status union therefore\n //? stays `'pass' | 'fail' | 'xfail' | 'xpass'` — no `'skipped'` variant.\n //? If you need to track unfilled stubs, scan `discoverCustomTestFiles()` and\n //? compare against the results array.\n if (!Array.isArray(exported) || exported.length === 0) {\n continue;\n }\n\n for (const rawCase of exported) {\n const c = rawCase as CustomTestCase;\n if (typeof c.name !== 'string' || typeof c.run !== 'function') continue;\n const expectedToFail = typeof c.expectedToFail === 'string' ? c.expectedToFail : undefined;\n const built = buildContext(discovery, input);\n const started = Date.now();\n const [runError] = await tryCatch(async () => c.run(built.ctx));\n //? Always close watchers regardless of pass/fail so the next case\n //? starts clean. tryCatch on the cleanup itself prevents a teardown\n //? throw from masking the original failure.\n await tryCatch(() => built.closeAllWatchers());\n //? Best-effort session cleanup: if `session.login()` minted a Redis\n //? session but the case didn't call `session.logout()`, the token\n //? accumulates until its TTL expires. Delete it now to avoid a slow\n //? session-key leak across a full sweep run.\n const residualToken = built.state.token;\n if (residualToken) {\n await tryCatch(async () => {\n const { deleteSession } = await import('@luckystack/login');\n await deleteSession(residualToken);\n });\n }\n const durationMs = Date.now() - started;\n\n let r: CustomTestResult;\n if (runError) {\n //? Augment the assertion message with the server's actual errorCode so\n //? the report explains the rejection, not just the failed expectation.\n const errorCode = extractErrorCode(built.state.lastResponse);\n const reason = errorCode ? `${runError.message} (server: ${errorCode})` : runError.message;\n r = expectedToFail\n ? { routePath, caseName: c.name, status: 'xfail', durationMs, reason, expectedToFail }\n : { routePath, caseName: c.name, status: 'fail', durationMs, reason };\n } else {\n r = expectedToFail\n ? {\n routePath, caseName: c.name, status: 'xpass', durationMs,\n reason: `marked expectedToFail (\"${expectedToFail}\") but passed — remove the marker`,\n expectedToFail,\n }\n : { routePath, caseName: c.name, status: 'pass', durationMs };\n }\n results.push(r);\n input.onResult?.(r);\n }\n }\n\n return {\n total: results.length,\n passed: results.filter(r => r.status === 'pass').length,\n failed: results.filter(r => r.status === 'fail').length,\n xfailed: results.filter(r => r.status === 'xfail').length,\n xpassed: results.filter(r => r.status === 'xpass').length,\n results,\n };\n};\n\n//? Public direct Layer-5 path: custom modules are imported only after env files\n//? and optional secret pointers resolve. The process-wide orchestrator calls the\n//? internal prepared function above, so it neither reloads pointers nor exposes\n//? a public `skipBootstrap` flag that callers could misuse accidentally.\nexport const runCustomTests = async (input: RunCustomTestsInput): Promise<RunCustomTestsSummary> => {\n assertProjectConfigLoader(input, 'runCustomTests');\n await resolveTestEnvironment({ loadProjectConfig: input.loadProjectConfig });\n return runCustomTestsAfterEnvironmentPrepared(input);\n};\n","//? Second-socket harness for Layer 5 custom tests. Opens a separate\n//? socket.io-client connection (socket B), joins the receiver room as a\n//? pure subscriber, and exposes the broadcast/streamTo chunk stream for\n//? assertions. Without this, `ctx.callSync` only surfaces the final\n//? envelope — chunks (LLM tokens, progress ticks) emitted via\n//? `broadcastStream` / `streamTo` flow on the `sync` event channel to\n//? room members other than the request originator's socket.\n//?\n//? Keep typed generics at this test seam; do not erase stream payload types.\n//?\n//? Wire shape, sourced from `@luckystack/sync` `_shared/streamEmitters.ts`:\n//? io.to(receiver).emit('sync', { ...payload, cb, fullName, status: 'stream' })\n//? where `fullName` = `sync/<page>/<name>/<version>`. We filter incoming\n//? frames by that `fullName` so a watcher only sees chunks for the route\n//? its test file lives next to.\n\nimport { socketEventNames, tryCatch } from '@luckystack/core';\n\nimport { connectTestSocket, joinTestRoom } from './testSocket';\n\nexport interface StreamChunkFrame {\n /** Per-request correlation id stamped by the server. */\n cb?: string;\n /** Full route name (`sync/<page>/<name>/<version>`). */\n fullName?: string;\n /** Always `'stream'` for chunk frames. */\n status?: 'stream';\n /** Payload fields spread on top of the envelope. */\n [key: string]: unknown;\n}\n\nexport interface StreamWatcher<TChunk extends StreamChunkFrame = StreamChunkFrame> {\n /** Chunks received so far. Array reference is stable; entries are pushed in arrival order. */\n readonly chunks: TChunk[];\n /** Wait until `predicate(chunks)` returns true, or until timeout (default 5000ms). */\n stopAt(predicate: (chunks: TChunk[]) => boolean, timeoutMs?: number): Promise<void>;\n /** Wait until N total chunks received, or until timeout. */\n waitForCount(n: number, timeoutMs?: number): Promise<void>;\n /** Close socket B. Tests SHOULD call this in cleanup; ctx auto-closes all watchers when the test ends. */\n close(): Promise<void>;\n}\n\nexport interface OpenStreamWatcherInput {\n /** Server URL (same as `ctx`'s `baseUrl`). */\n baseUrl: string;\n /** Room to join as a pure subscriber. */\n roomCode: string;\n /** Active session token (used for `joinRoom` auth). May be null for routes that don't require auth. */\n token: string | null;\n /**\n * Resolved route name `sync/<page>/<name>/<version>` — chunk frames are\n * filtered by their `fullName` property so a watcher only surfaces chunks\n * for the route its test is bound to.\n */\n routeFullName: string;\n /** Default 5000ms — overridable per `stopAt` / `waitForCount` call. */\n defaultTimeoutMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5000;\nconst LABEL = 'watchStream';\n\nconst isStreamChunkFrame = (value: unknown): value is StreamChunkFrame =>\n typeof value === 'object' && value !== null;\n\nexport const openStreamWatcher = async <TChunk extends StreamChunkFrame = StreamChunkFrame>(\n input: OpenStreamWatcherInput,\n): Promise<StreamWatcher<TChunk>> => {\n const defaultTimeoutMs = input.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;\n const chunks: TChunk[] = [];\n const waiters: { predicate: (chunks: TChunk[]) => boolean; resolve: () => void }[] = [];\n\n //? `connectTestSocket` tears the socket down itself when the connect fails,\n //? so a failed attempt never leaks a socket the caller cannot close.\n const socket = await connectTestSocket({ baseUrl: input.baseUrl, token: input.token, label: LABEL });\n\n const handleChunk = (raw: unknown): void => {\n if (!isStreamChunkFrame(raw)) return;\n if (raw.status !== 'stream') return;\n if (raw.fullName !== input.routeFullName) return;\n //? Server emits chunk frames typed as `StreamChunkFrame` — caller's\n //? `TChunk` narrows the payload shape (e.g. `{ chunk: string }` for\n //? `streamBroadcast`). The runtime guards above confirm the wire shape;\n //? narrowing the static type is the test author's responsibility.\n const chunk = raw as TChunk;\n chunks.push(chunk);\n //? Drain waiters whose predicates are now satisfied. Iterate in reverse\n //? so we can splice without breaking indices.\n for (let i = waiters.length - 1; i >= 0; i--) {\n const waiter = waiters[i];\n if (!waiter) continue;\n if (waiter.predicate(chunks)) {\n waiters.splice(i, 1);\n waiter.resolve();\n }\n }\n };\n\n socket.on(socketEventNames.sync, handleChunk);\n\n //? A rejected / unanswered join must tear the socket (and its `sync`\n //? listener) down — the caller only tracks the watcher after this resolves.\n const [joinError] = await tryCatch(() => joinTestRoom(socket, input.roomCode, { label: LABEL }));\n if (joinError) {\n socket.off(socketEventNames.sync, handleChunk);\n socket.disconnect();\n throw joinError;\n }\n\n let closed = false;\n const close = (): Promise<void> => {\n if (closed) return Promise.resolve();\n closed = true;\n socket.off(socketEventNames.sync, handleChunk);\n //? Drain any outstanding waiters so callers don't hang past test end.\n for (const waiter of waiters) waiter.resolve();\n waiters.length = 0;\n socket.disconnect();\n return Promise.resolve();\n };\n\n const stopAt = (\n predicate: (chunks: TChunk[]) => boolean,\n timeoutMs: number = defaultTimeoutMs,\n ): Promise<void> => {\n if (predicate(chunks)) return Promise.resolve();\n return new Promise<void>((resolve, reject) => {\n let settled = false;\n const waiter = {\n predicate,\n resolve: () => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve();\n },\n };\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n const idx = waiters.indexOf(waiter);\n if (idx !== -1) waiters.splice(idx, 1);\n reject(new Error(\n `watchStream.stopAt: predicate not satisfied within ${timeoutMs}ms (received ${chunks.length} chunks)`,\n ));\n }, timeoutMs);\n waiters.push(waiter);\n });\n };\n\n const waitForCount = (n: number, timeoutMs: number = defaultTimeoutMs): Promise<void> => {\n return stopAt((current) => current.length >= n, timeoutMs);\n };\n\n return {\n chunks,\n stopAt,\n waitForCount,\n close,\n };\n};\n","//? Orchestrator — runs all five test layers (contract, auth-enforcement,\n//? rate-limit, fuzz, custom) against a running server and returns a single\n//? combined summary. Consumers call this from their own thin script that\n//? imports the generated `apiMethodMap` + `apiInputSchemas` (and, to sweep\n//? sync routes too, `syncMetaMap` + `syncInputSchemas`).\n\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { Socket } from 'socket.io-client';\nimport { clearAllRateLimits, getCsrfConfig, getProjectConfig, tryCatch, tryCatchSync } from '@luckystack/core';\n\nimport { createSampleInputFor } from './sampleInputFor';\nimport type { InputSchemaMap } from './sampleInputFor';\nimport { createTestDataMarker } from './testDataMarker';\nimport { resetServerState } from './resetServerState';\nimport { createHttpProbeTransport } from './probeRequest';\nimport type { ProbeTransport } from './probeRequest';\nimport { createSocketProbeTransport } from './socketProbeTransport';\nimport { connectTestSocket, joinTestRoom, releaseTestSocket } from './testSocket';\nimport { runContractTests } from './runContractTests';\nimport { runAuthEnforcementTests } from './runAuthEnforcementTests';\nimport { runRateLimitTests } from './runRateLimitTests';\nimport { runCsrfEnforcementTests } from './runCsrfEnforcementTests';\nimport { runFuzzTests } from './runFuzzTests';\nimport { runCustomTestsAfterEnvironmentPrepared } from './customTests';\nimport {\n assertProjectConfigLoader,\n resolveTestEnvironment,\n type ResolveTestEnvironmentInput,\n} from './resolveTestEnvironment';\nimport { LAYER_KEYS } from './testLayerHelpers';\nimport type { CustomTestResult, RunCustomTestsSummary } from './customTests';\nimport type { ApiMethodMap, ApiMetaMap, EndpointDescriptor, RunContractSummary, SyncMetaMap } from './types';\n\nexport interface RunAllTestsInput {\n apiMethodMap: ApiMethodMap;\n /**\n * Generated `syncMetaMap` (from `apiTypes.generated.ts`). When given, the\n * sweep layers exercise every sync route too; without it sync routes are\n * not swept (a warning says so).\n */\n syncMetaMap?: SyncMetaMap;\n /** Generated `syncInputSchemas` (from `apiInputSchemas.generated.ts`) — Zod-valid sync sample payloads. */\n syncInputSchemas?: InputSchemaMap;\n /**\n * How sync routes are probed. Defaults to the project's own\n * `transport.invocation`: `'socket'` → Socket.io (the pipeline real clients\n * hit), `'routed-http'` → the `/sync/*` HTTP route. The CSRF layer always\n * probes over HTTP — CSRF is an HTTP-only guard.\n */\n syncTransport?: 'socket' | 'http';\n /**\n * Write the full `RunAllTestsSummary` as JSON to this path (relative to the\n * process cwd). Defaults to `TEST_OUTPUT_FILE`, else `test-results.json`;\n * `false` disables the file.\n */\n outputFile?: string | false;\n /**\n * Lazy loader for the consumer's default `config.ts` export. Required so\n * runAllTests cannot silently skip a configured secret manager; resolution\n * happens in THIS process before any layer or env-backed client is touched.\n */\n loadProjectConfig: NonNullable<ResolveTestEnvironmentInput['loadProjectConfig']>;\n apiMetaMap: ApiMetaMap;\n apiInputSchemas: InputSchemaMap;\n baseUrl: string;\n /** Cookie name for the session token. Defaults to the project's configured session cookie name (`projectConfig.http.sessionCookieName`). */\n sessionCookieName?: string;\n /** Auth token applied as a `Cookie` header to sweep-layer requests that need a session. */\n authToken?: string;\n /** Endpoints to skip from the sweep layers, e.g. routes that need a file upload. */\n skip?: string[];\n /** Substring filter applied to `<page>/<name>/<version>`. */\n filter?: string;\n /** Disable individual layers (faster local iteration). */\n noSweep?: boolean;\n noFuzz?: boolean;\n noRateLimit?: boolean;\n /**\n * Disable the CSRF-enforcement layer. Defaults to ON (the layer runs) — but it\n * can only probe past the auth guard with a session, so it is a no-op unless an\n * `authToken` is supplied.\n */\n noCsrf?: boolean;\n noCustom?: boolean;\n /**\n * Token sent as `X-Test-Reset-Token` when the rate-limit layer resets the\n * shared bucket between endpoints. The server's `/_test/reset` requires\n * `TEST_RESET_TOKEN` to be set and matching — an unset/wrong token is 403, so\n * `resetBetweenEndpoints` would silently no-op. Defaults to\n * `process.env.TEST_RESET_TOKEN` so a CI run with the env set works with no\n * extra wiring; pass explicitly to override.\n */\n resetToken?: string;\n /**\n * When a reset token is available, the sweep POSTs\n * `/_test/reset?only=rateLimits` ONCE before the first layer (no bucket left\n * drained by an earlier run) and ONCE after the last (the custom layer and the\n * next run start unthrottled). Only limiter state is cleared: sessions — the\n * `authToken` session above all — survive, so `TEST_RESET_TOKEN` can be set\n * on an authenticated sweep. The reset never touches the app database.\n * Defaults to ON; set `false` to opt out. With no token the bookends are\n * skipped with a logged note (they can't run — `/_test/reset` 403s).\n */\n resetBookends?: boolean;\n}\n\nexport interface RunAllTestsSummary {\n contract?: RunContractSummary;\n auth?: RunContractSummary;\n rateLimit?: RunContractSummary;\n csrf?: RunContractSummary;\n fuzz?: RunContractSummary;\n custom?: RunCustomTestsSummary;\n totalPassed: number;\n totalFailed: number;\n}\n\n//? Decide whether the contract-sweep reset bookends should run. Returns the\n//? token to use, or null to skip. When the bookends are requested (the default)\n//? but no token is available, warn once — `/_test/reset` requires the token and\n//? would 403, so the bookends silently can't run; the sweep proceeds unguarded.\nconst resolveResetBookendToken = (input: RunAllTestsInput): string | null => {\n if (input.resetBookends === false) return null;\n const token = input.resetToken ?? process.env.TEST_RESET_TOKEN;\n if (!token) {\n console.warn(\n '[test-runner] runAllTests: reset bookends skipped — no reset token '\n + '(set TEST_RESET_TOKEN or pass resetToken/resetBookends). /_test/reset '\n + 'requires it, so the clean-slate + cleanup resets around the sweep are skipped.',\n );\n return null;\n }\n return token;\n};\n\n//? Build the Cookie (and CSRF) headers sweep layers send when an `authToken`\n//? is supplied. In cookie-mode the server's CSRF middleware rejects POST/PUT/DELETE\n//? without a matching CSRF token, so we look up the session record and attach the\n//? token the same way customTests.ts does: call getSession(authToken) and read\n//? csrfToken off the persisted record. Token-mode sweeps (no Cookie header) and\n//? runs without authToken are unaffected — the CSRF header is never added.\nconst buildAuthHeaders = async (input: RunAllTestsInput): Promise<Record<string, string>> => {\n const headers: Record<string, string> = {};\n if (!input.authToken) return headers;\n\n const cookieName = input.sessionCookieName ?? getProjectConfig().http.sessionCookieName;\n headers.Cookie = `${cookieName}=${input.authToken}`;\n\n //? Resolve the CSRF token lazily — import @luckystack/login at call time so\n //? projects that run the test-runner without login installed don't crash on\n //? import. Mirror the same dynamic import pattern used in customTests.ts.\n try {\n const { getSession } = await import('@luckystack/login');\n const session = await getSession(input.authToken);\n if (session?.csrfToken) {\n headers[getCsrfConfig().headerName] = session.csrfToken;\n }\n } catch (error) {\n //? @luckystack/login not installed or session lookup failed.\n //? Warn so the operator knows the sweep is running without a CSRF header\n //? (degraded mode) — the sweep continues, but CSRF-protected state-changing\n //? endpoints will be unreachable in cookie-mode.\n console.warn(\n '[test-runner] buildAuthHeaders: could not resolve CSRF token —',\n error instanceof Error ? error.message : String(error),\n '— sweep will run without the CSRF header (degraded mode).',\n );\n }\n\n return headers;\n};\n\ninterface SyncSweepTransports {\n /** Transport the contract / auth / rate-limit / fuzz layers use for sync routes. */\n sweep: ProbeTransport;\n /** HTTP transport the CSRF layer uses for sync routes (CSRF is HTTP-only). */\n csrf: ProbeTransport;\n close: () => Promise<void>;\n}\n\n//? Prepare sync-route probing: one throwaway room per run that every sync probe\n//? targets, joined by the sweep's session so room-membership enforcement\n//? (`sync.requireRoomMembership`, default on) sees a member — exactly what a\n//? real client does before syncing. The HTTP pipeline reads membership from\n//? the session's persisted room codes, which the join writes; the socket\n//? transport additionally joins on its own socket. Without an `authToken`\n//? nothing can join, so sync probes see what an anonymous client sees.\nconst prepareSyncSweep = async (input: RunAllTestsInput): Promise<SyncSweepTransports> => {\n const receiver = `lstest-room-${crypto.randomUUID()}`;\n const sessionCookieName = input.sessionCookieName ?? getProjectConfig().http.sessionCookieName;\n const mode = input.syncTransport\n ?? (getProjectConfig().transport.invocation === 'routed-http' ? 'http' : 'socket');\n const http = createHttpProbeTransport({ syncReceiver: receiver });\n\n let memberSocket: Socket | null = null;\n if (input.authToken) {\n const authToken = input.authToken;\n const [connectError, socket] = await tryCatch(() => connectTestSocket({\n baseUrl: input.baseUrl,\n token: authToken,\n label: 'sync sweep',\n sessionCookieName,\n }));\n if (connectError || !socket) {\n console.warn(`[test-runner] runAllTests: could not open a socket for the sync sweep — ${connectError?.message ?? 'no socket'}; sync probes will be rejected as non-members.`);\n } else {\n memberSocket = socket;\n const [joinError] = await tryCatch(() => joinTestRoom(socket, receiver, { label: 'sync sweep' }));\n if (joinError) console.warn(`[test-runner] runAllTests: ${joinError.message}; sync probes will be rejected as non-members.`);\n }\n }\n\n const sweep = mode === 'socket'\n ? createSocketProbeTransport({ baseUrl: input.baseUrl, receiver, sessionCookieName })\n : http;\n\n return {\n sweep,\n csrf: http,\n close: async () => {\n await sweep.close?.();\n //? The member socket runs on the caller's `authToken` session — release\n //? it so presence does not log that session out after the sweep.\n if (memberSocket) await releaseTestSocket(memberSocket);\n },\n };\n};\n\n//? Run the auto-sweep layers (contract → auth → rate-limit → csrf → fuzz) in\n//? order, honoring the per-layer disable flags, and write each summary onto\n//? `summary`. Every layer receives `filter`, so a filtered run only REQUESTS\n//? the matching routes (it used to sweep everything and filter the report).\nconst runSweepLayers = async (\n input: RunAllTestsInput,\n summary: RunAllTestsSummary,\n headers: Record<string, string>,\n inputFor: (endpoint: EndpointDescriptor) => unknown,\n): Promise<void> => {\n const resetToken = input.resetToken ?? process.env.TEST_RESET_TOKEN;\n //? Reset bookend — BEFORE the sweep: no limiter bucket left drained by an\n //? earlier run. Limiter state only — the sweep's own session must survive.\n const resetBookendToken = resolveResetBookendToken(input);\n if (resetBookendToken) {\n await resetServerState({ baseUrl: input.baseUrl, token: resetBookendToken, only: ['rateLimits'] });\n }\n\n const hasSyncRoutes = Object.keys(input.syncMetaMap ?? {}).length > 0;\n const sync = hasSyncRoutes ? await prepareSyncSweep(input) : null;\n const shared = {\n apiMethodMap: input.apiMethodMap,\n syncMetaMap: input.syncMetaMap,\n baseUrl: input.baseUrl,\n skip: input.skip,\n filter: input.filter,\n syncTransport: sync?.sweep,\n };\n\n //? The layers must not leave sockets open when one throws.\n const [layerError] = await tryCatch(async () => {\n summary.contract = await runContractTests({ ...shared, headers, inputFor });\n\n summary.auth = await runAuthEnforcementTests({ ...shared, apiMetaMap: input.apiMetaMap, inputFor });\n\n if (!input.noRateLimit) {\n summary.rateLimit = await runRateLimitTests({\n ...shared,\n apiMetaMap: input.apiMetaMap,\n headers,\n inputFor,\n //? Reset the shared per-IP rate-limit bucket between endpoints so a\n //? neighbour saturating the window doesn't cause false pass/fail on the\n //? next endpoint. Requires the server to expose /_test/reset (NODE_ENV in\n //? { 'development', 'test' } AND TEST_RESET_TOKEN set + matching header).\n resetBetweenEndpoints: true,\n //? Thread the reset token (explicit input, else the TEST_RESET_TOKEN env the\n //? server validates against) — without it `/_test/reset` returns 403 and the\n //? between-endpoint reset silently never happens.\n resetToken,\n });\n }\n\n //? CSRF-enforcement layer: probes that the framework's CSRF middleware rejects\n //? state-changing authenticated requests carrying NO CSRF header. It can only\n //? reach the CSRF check past the auth guard with a valid session, so it runs\n //? only when an `authToken` is supplied (and the layer isn't disabled). Without\n //? a token there is nothing to probe — the layer is silently skipped, leaving\n //? behavior unchanged for tokenless sweeps.\n if (!input.noCsrf && headers.Cookie) {\n summary.csrf = await runCsrfEnforcementTests({\n ...shared,\n apiMetaMap: input.apiMetaMap,\n authCookie: headers.Cookie,\n inputFor,\n syncTransport: sync?.csrf,\n });\n }\n\n if (!input.noFuzz) {\n summary.fuzz = await runFuzzTests({ ...shared, headers });\n }\n });\n\n await sync?.close();\n\n //? Reset bookend — AFTER the sweep: the custom layer and the next run start\n //? with fresh limiter buckets.\n if (resetBookendToken) {\n await resetServerState({ baseUrl: input.baseUrl, token: resetBookendToken, only: ['rateLimits'] });\n }\n\n if (layerError) throw layerError;\n};\n\n//? Run the custom (Layer 5) sweep, after clearing the per-route rate-limit\n//? buckets the sweep layers drained. Mutates `summary` in place — identical to\n//? the prior inline block.\nconst runCustomLayer = async (input: RunAllTestsInput, summary: RunAllTestsSummary): Promise<void> => {\n //? The rate-limit layer drains per-route buckets; clear them before the\n //? custom layer so business-logic tests on low-limit routes (e.g.\n //? confirmReset, sendReset) aren't spuriously rejected with\n //? api.rateLimitExceeded. Runs in-process against the same Redis the server\n //? uses. Non-fatal if the active strategy has no clear().\n try {\n await clearAllRateLimits();\n } catch {\n //? swallow — a missing clear() is a test-quality degrade, not a crash.\n }\n const custom = await runCustomTestsAfterEnvironmentPrepared({\n baseUrl: input.baseUrl,\n sessionCookieName: input.sessionCookieName,\n filter: input.filter,\n //? Pass the generated method map so the harness invokes each API route\n //? with its declared HTTP method (e.g. logout is DELETE, not POST).\n apiMethodMap: input.apiMethodMap,\n });\n summary.custom = custom;\n};\n\n//? The ordered list of sweep-layer keys that map to `RunContractSummary`\n//? slots on `RunAllTestsSummary`. Drives `computeTotals` and the reporter so\n//? adding a new sweep layer only requires adding it here + to the summary type.\nconst SWEEP_LAYER_ORDER = [\n LAYER_KEYS.contract,\n LAYER_KEYS.auth,\n LAYER_KEYS.rateLimit,\n LAYER_KEYS.csrf,\n LAYER_KEYS.fuzz,\n] as const;\n\ntype SweepLayerKey = typeof SWEEP_LAYER_ORDER[number];\n\n//? Map from each canonical layer key to the matching summary property name on\n//? `RunAllTestsSummary`. Property names diverged from LAYER_KEYS values early\n//? (LAYER_KEYS uses display strings like `'auth-enforcement'`); keeping this\n//? map here means neither the reporter nor computeTotals need to hand-code the\n//? mapping.\nconst SWEEP_SUMMARY_PROP: Record<SweepLayerKey, keyof Pick<RunAllTestsSummary, 'contract' | 'auth' | 'rateLimit' | 'csrf' | 'fuzz'>> = {\n [LAYER_KEYS.contract]: 'contract',\n [LAYER_KEYS.auth]: 'auth',\n [LAYER_KEYS.rateLimit]: 'rateLimit',\n [LAYER_KEYS.csrf]: 'csrf',\n [LAYER_KEYS.fuzz]: 'fuzz',\n};\n\n//? Iterate every sweep layer + the custom layer to sum passed/failed.\n//? DD-TR-various — xpass exit-code policy (documented decision):\n//? `xpass` (a case marked `expectedToFail` that unexpectedly passes) is NOT\n//? counted in `totalFailed`. Rationale: an xpass is a positive signal — the\n//? known issue is gone and the marker just needs removing. Treating it as a\n//? failure would make CI red when a bug is fixed, which is backwards.\n//? It IS shown prominently in the summary line and the \"Unexpectedly passed\"\n//? section so the stale marker can't rot silently behind a green run.\n//? To opt into strict mode (xpass = CI failure), inspect `summary.custom?.xpassed`\n//? in your own script and `process.exit(1)` when it is > 0.\nconst computeTotals = (summary: RunAllTestsSummary): void => {\n let passed = 0;\n let failed = 0;\n for (const key of SWEEP_LAYER_ORDER) {\n const s = summary[SWEEP_SUMMARY_PROP[key]];\n passed += s?.passed ?? 0;\n failed += s?.failed ?? 0;\n }\n summary.totalPassed = passed + (summary.custom?.passed ?? 0);\n summary.totalFailed = failed + (summary.custom?.failed ?? 0);\n};\n\nexport const runAllTests = async (input: RunAllTestsInput): Promise<RunAllTestsSummary> => {\n assertProjectConfigLoader(input, 'runAllTests');\n //? The live server resolves its own env, but Layer-5 tests may use ctx.prisma\n //? or Redis directly in THIS process. Resolve before importing custom tests or\n //? constructing those clients, otherwise DATABASE_URL_V<n> reaches Prisma raw.\n await resolveTestEnvironment({ loadProjectConfig: input.loadProjectConfig });\n\n const summary: RunAllTestsSummary = { totalPassed: 0, totalFailed: 0 };\n\n //? Warn when a non-empty apiMethodMap is paired with an empty apiMetaMap.\n //? The auth-enforcement and rate-limit layers both use the meta map to decide\n //? which routes require login and which have declared rate limits. An absent\n //? or stale map silently skips every auth/rate-limit check — the run looks\n //? green but provides no real coverage.\n if (!input.noSweep) {\n const hasRoutes = Object.keys(input.apiMethodMap).length > 0;\n const hasMeta = Object.keys(input.apiMetaMap).length > 0;\n if (hasRoutes && !hasMeta) {\n console.warn(\n '[test-runner] runAllTests: apiMetaMap is empty but apiMethodMap has routes. '\n + 'The auth-enforcement and rate-limit layers will skip every route. '\n + 'Pass the generated apiMetaMap to enable those checks.',\n );\n }\n //? Sync routes are only swept when the generated syncMetaMap is passed —\n //? say so instead of letting them silently go without transport coverage.\n if (!input.syncMetaMap) {\n console.warn(\n '[test-runner] runAllTests: no syncMetaMap passed — sync routes are NOT swept. '\n + 'Pass the generated syncMetaMap (+ syncInputSchemas) to cover them.',\n );\n }\n }\n\n const headers = await buildAuthHeaders(input);\n //? One marker per run so every generated string this sweep posts is tagged\n //? with the same recognizable `lstest_<uuid>_` prefix (finding #98).\n const inputFor = createSampleInputFor({\n apiInputSchemas: input.apiInputSchemas,\n syncInputSchemas: input.syncInputSchemas,\n stringPrefix: createTestDataMarker(),\n });\n\n if (!input.noSweep) {\n await runSweepLayers(input, summary, headers, inputFor);\n }\n\n if (!input.noCustom) {\n await runCustomLayer(input, summary);\n }\n\n computeTotals(summary);\n writeSummaryFile(summary, input.outputFile);\n\n return summary;\n};\n\n//? Machine-readable results, so CI or an AI agent can read pass/fail + the\n//? per-result reasons without scraping the terminal. A write failure is\n//? reported, never thrown — the summary is still returned to the caller.\nconst writeSummaryFile = (summary: RunAllTestsSummary, outputFile: string | false | undefined): void => {\n if (outputFile === false) return;\n const target = path.resolve(outputFile ?? process.env.TEST_OUTPUT_FILE ?? 'test-results.json');\n const [writeError] = tryCatchSync(() => {\n fs.writeFileSync(target, JSON.stringify(summary, null, 2), 'utf8');\n });\n if (writeError) {\n console.warn(`[test-runner] runAllTests: could not write ${target} — ${writeError.message}`);\n return;\n }\n console.log(`[test-runner] full results written to ${target}`);\n};\n\n//? ── Colored, list-based reporting ───────────────────────────────────────\n//? Honors NO_COLOR / FORCE_COLOR (https://no-color.org) and falls back to\n//? plain text when stdout is not a TTY (piped to a file / CI log).\nconst useColor = ((): boolean => {\n if (process.env.NO_COLOR) return false;\n if (process.env.FORCE_COLOR) return true;\n return process.stdout.isTTY;\n})();\nconst paint = (code: string, text: string): string => (useColor ? `\\u001B[${code}m${text}\\u001B[0m` : text);\nconst green = (t: string): string => paint('32', t);\nconst red = (t: string): string => paint('31', t);\nconst yellow = (t: string): string => paint('33', t);\nconst dim = (t: string): string => paint('2', t);\nconst bold = (t: string): string => paint('1', t);\n\ninterface ReportRow {\n label: string;\n reason?: string;\n}\n\n//? DD-TR-various — leaky summary union tightened:\n//? `LayerSummary` is the minimal shape the reporter cares about — the fields\n//? every layer summary has in common. The extra-field helpers discriminate via\n//? `in` to stay type-safe without widening callers to `unknown`.\ninterface LayerSummary {\n total: number;\n passed: number;\n failed: number;\n}\n\nconst xfailOf = (s: LayerSummary | undefined): number => {\n if (s === undefined || !('xfailed' in s)) return 0;\n const v = (s as Record<string, unknown>).xfailed;\n return typeof v === 'number' ? v : 0;\n};\nconst skippedOf = (s: LayerSummary | undefined): number => {\n if (s === undefined || !('skipped' in s)) return 0;\n const v = (s as Record<string, unknown>).skipped;\n return typeof v === 'number' ? v : 0;\n};\n\n//? Per-layer headline: green \"X/Y passed\" when clean, red \"Z failed\" when not,\n//? plus dim xfail / skipped counts. This is the \"14/20 in groen, 6/20 in rood\"\n//? the report leads with.\nconst formatLayerLine = (label: string, s: LayerSummary | undefined): string => {\n if (!s) return ` ${dim('–')} ${label.padEnd(18)} ${dim('(layer skipped)')}`;\n const clean = s.failed === 0;\n const mark = clean ? green('✓') : red('✗');\n const head = clean\n ? green(`${s.passed}/${s.total} passed`)\n : `${green(`${s.passed}/${s.total} passed`)} ${red(`${s.failed}/${s.total} failed`)}`;\n const extras: string[] = [];\n const xf = xfailOf(s);\n const sk = skippedOf(s);\n if (xf) extras.push(yellow(`${xf} expected-fail`));\n if (sk) extras.push(dim(`${sk} skipped`));\n const extra = extras.length > 0 ? ` ${dim('·')} ${extras.join(dim(', '))}` : '';\n return ` ${mark} ${label.padEnd(18)} ${head}${extra}`;\n};\n\nconst sweepFailRows = (s: RunContractSummary | undefined): ReportRow[] =>\n (s?.results ?? [])\n .filter((r) => r.status === 'fail')\n .map((r) => ({\n label: `${r.endpoint.page}/${r.endpoint.name}/${r.endpoint.version}`,\n reason: r.errorCode ? `${r.reason ?? 'failed'} [${r.errorCode}]` : r.reason,\n }));\n\nconst sweepSkipRows = (layer: string, s: RunContractSummary | undefined): ReportRow[] =>\n (s?.results ?? [])\n .filter((r) => r.status === 'skipped')\n .map((r) => ({\n label: `${layer}: ${r.endpoint.page}/${r.endpoint.name}/${r.endpoint.version}`,\n reason: r.reason,\n }));\n\nconst customRows = (s: RunCustomTestsSummary | undefined, status: CustomTestResult['status']): ReportRow[] =>\n (s?.results ?? [])\n .filter((r) => r.status === status)\n .map((r) => ({ label: `${r.routePath} :: ${r.caseName}`, reason: r.reason }));\n\nconst printSection = (title: (t: string) => string, heading: string, rows: ReportRow[], bullet: string): void => {\n if (rows.length === 0) return;\n console.log('');\n console.log(title(bold(`${heading} (${rows.length})`)));\n for (const row of rows) {\n console.log(` ${title(bullet)} ${row.label}`);\n if (row.reason) console.log(` ${dim(row.reason)}`);\n }\n};\n\nexport const logRunAllSummary = (summary: RunAllTestsSummary): void => {\n console.log('');\n\n //? Print a headline line for every sweep layer (ordered) then the custom layer.\n for (const key of SWEEP_LAYER_ORDER) {\n console.log(formatLayerLine(key, summary[SWEEP_SUMMARY_PROP[key]]));\n }\n console.log(formatLayerLine(LAYER_KEYS.custom, summary.custom));\n\n //? Real failures across every layer — these MUST be fixed (a green test that\n //? went red, or a wrong test). Sweep failures + un-marked custom failures.\n const failed: ReportRow[] = [\n ...SWEEP_LAYER_ORDER.flatMap((key) => sweepFailRows(summary[SWEEP_SUMMARY_PROP[key]])),\n ...customRows(summary.custom, 'fail'),\n ];\n printSection(red, 'Failed — must be fixed (real bugs / wrong tests)', failed, '✗');\n\n //? Known, accepted failures — red is OK here; they are tracked, not regressions.\n printSection(yellow, 'Expected failures — known issues, allowed to fail', customRows(summary.custom, 'xfail'), '⚠');\n\n //? Stale markers — these passed but are still flagged expectedToFail.\n printSection(yellow, 'Unexpectedly passed — remove the expectedToFail marker', customRows(summary.custom, 'xpass'), '?');\n\n //? Skipped with reason — not run in this mode (login-gated, high rate-limit, …).\n const skipped: ReportRow[] = SWEEP_LAYER_ORDER.flatMap((key) =>\n sweepSkipRows(key, summary[SWEEP_SUMMARY_PROP[key]]),\n );\n printSection(dim, 'Skipped — not run (with reason)', skipped, '–');\n\n //? Sum xfail and skipped across all layers (sweep + custom) in one pass.\n let totalXfail = 0;\n let totalSkipped = 0;\n for (const key of SWEEP_LAYER_ORDER) {\n const s = summary[SWEEP_SUMMARY_PROP[key]];\n totalXfail += xfailOf(s);\n totalSkipped += skippedOf(s);\n }\n totalXfail += xfailOf(summary.custom);\n totalSkipped += skippedOf(summary.custom);\n\n //? `xpass` (a case still marked expectedToFail that now passes) is NOT a\n //? failure, so it doesn't move the exit code — but it MUST be visible in the\n //? final line, otherwise a stale marker rots silently behind a green run.\n const totalXpass = summary.custom?.xpassed ?? 0;\n\n console.log('');\n const parts = [\n green(`${summary.totalPassed} passed`),\n summary.totalFailed > 0 ? red(`${summary.totalFailed} failed`) : dim('0 failed'),\n totalXfail > 0 ? yellow(`${totalXfail} expected-fail`) : dim('0 expected-fail'),\n totalXpass > 0 ? yellow(`${totalXpass} unexpectedly-passed`) : dim('0 unexpectedly-passed'),\n totalSkipped > 0 ? yellow(`${totalSkipped} skipped`) : dim('0 skipped'),\n ];\n console.log(`Summary: ${parts.join(dim(' · '))}`);\n console.log(dim(' legend: ✗ red = must fix · ⚠ yellow = known/allowed · – skipped (not run)'));\n};\n","//? Extension registry for the test runner. Consumers plug in custom test\n//? layers, realistic-payload fixtures, and result reporters without\n//? forking the framework.\n//?\n//? Three slots:\n//?\n//? 1. Test layers — additional checks per endpoint (CORS enforcement,\n//? business rules, custom auth schemes, multi-tenant isolation).\n//? 2. Fixtures — realistic input payloads (valid + invalid) per typeKey\n//? so the fuzz layer can use them before falling back to schema-random.\n//? 3. Reporters — per-result and per-summary callbacks + optional\n//? webhook URL the runner POSTs the batch summary to.\n//?\n//? All slots are append-only at registration time; clearing requires\n//? `resetTestExtensionsForTests()` (kept out of the production surface\n//? to prevent accidental wipes from a misbehaving plugin).\n\nexport interface TestLayerInput {\n /** Resolved route name (`api/billing/getInvoice/v1` or `sync/chat/send/v1`). */\n endpoint: string;\n /** Method when applicable, undefined for sync endpoints. */\n method?: string;\n /** Bearer token / session token to use for auth-aware checks. */\n authToken?: string;\n /**\n * A Zod-valid sample `data` payload for this route — generated from the\n * route's input schema when `runRegisteredLayers` received the generated\n * schemas (or an `inputFor`), `{}` otherwise. The same payload the built-in\n * contract sweep sends, so a layer can exercise the route past validation.\n */\n sampleInput?: unknown;\n}\n\nexport interface TestLayerResult {\n passed: boolean;\n /** Short reason key — i18n-style errorCode when failed. */\n message?: string;\n /** Optional extras for the reporter (latency, response sample, ...). */\n metadata?: Record<string, unknown>;\n}\n\nexport interface TestLayer {\n name: string;\n run: (input: TestLayerInput) => Promise<TestLayerResult> | TestLayerResult;\n}\n\nexport interface TestFixture<TPayload = unknown> {\n valid: TPayload[];\n invalid: TPayload[];\n}\n\nexport interface TestResult {\n layer: string;\n endpoint: string;\n passed: boolean;\n message?: string;\n durationMs?: number;\n metadata?: Record<string, unknown>;\n}\n\nexport interface TestSummary {\n totalLayers: number;\n totalEndpoints: number;\n passed: number;\n failed: number;\n results: TestResult[];\n}\n\nexport interface TestReporter {\n /** Called once per individual TestResult as the runner progresses. */\n onResult?: (result: TestResult) => void | Promise<void>;\n /** Called once at the end with the aggregated summary. */\n onSummary?: (summary: TestSummary) => void | Promise<void>;\n /** Optional webhook the runner POSTs the summary to. */\n webhookUrl?: string;\n webhookAuth?: { type: 'bearer'; token: string };\n}\n\nconst layers = new Map<string, TestLayer>();\nconst fixtures = new Map<string, TestFixture>();\nlet reporter: TestReporter | null = null;\n\n/**\n * Register a custom test layer. Re-registering the same name replaces\n * the previous entry. Use for CORS enforcement, business-rule checks,\n * custom auth schemes, multi-tenant isolation, GDPR data-flow checks.\n */\nexport const registerTestLayer = (layer: TestLayer): void => {\n layers.set(layer.name, layer);\n};\n\nexport const listTestLayers = (): TestLayer[] => [...layers.values()];\n\n/**\n * Register a realistic payload fixture for a specific type key. The fuzz\n * layer prefers these over schema-random generated inputs when a fixture\n * exists for the endpoint's input type.\n *\n * `valid` payloads are used to verify happy-path behavior; `invalid`\n * payloads probe the validator's error responses.\n */\nexport const registerTestFixture = <TPayload = unknown>(typeKey: string, fixture: TestFixture<TPayload>): void => {\n fixtures.set(typeKey, fixture);\n};\n\nexport const getTestFixture = (typeKey: string): TestFixture | undefined => fixtures.get(typeKey);\n\n/**\n * Register a test reporter. Set `null` to unregister. The runner calls\n * `onResult` per individual result as the run progresses and `onSummary`\n * once at the end. When `webhookUrl` is set, the runner additionally\n * POSTs the JSON-serialised summary to that URL.\n */\nexport const registerTestReporter = (newReporter: TestReporter | null): void => {\n reporter = newReporter;\n};\n\nexport const getTestReporter = (): TestReporter | null => reporter;\n\nexport const resetTestExtensionsForTests = (): void => {\n layers.clear();\n fixtures.clear();\n reporter = null;\n};\n","//? Runs every layer registered via `registerTestLayer` against every endpoint\n//? in the given `apiMethodMap`, fans each result to the registered reporter\n//? (`onResult` / `onSummary`), and — when the reporter declares a `webhookUrl`\n//? — POSTs the JSON-serialised summary to it.\n//?\n//? Built-in sweep layers (contract/auth/rate-limit/fuzz) are NOT run here and\n//? `runAllTests` does NOT call this function automatically. The extension registry\n//? is a coordination surface for consumer test harnesses: read the layers with\n//? `listTestLayers()`, then call `runRegisteredLayers({ apiMethodMap, authToken })`\n//? from your own aggregator after the built-in sweeps finish. See\n//? docs/extension-hooks.md for the full lifecycle and examples.\n\nimport { tryCatch, tryCatchSync } from '@luckystack/core';\n\nimport { listTestLayers, getTestReporter } from './extensionRegistry';\nimport { createSampleInputFor } from './sampleInputFor';\nimport type { InputSchemaMap } from './sampleInputFor';\nimport { createTestDataMarker } from './testDataMarker';\nimport { selectEndpoints, shouldSkip } from './testLayerHelpers';\nimport type { TestResult, TestSummary } from './extensionRegistry';\nimport type { ApiMethodMap, EndpointDescriptor, SyncMetaMap } from './types';\n\nexport interface RunRegisteredLayersInput {\n apiMethodMap: ApiMethodMap;\n /** Generated `syncMetaMap`. When given, layers also run against every sync route. */\n syncMetaMap?: SyncMetaMap;\n /** Session/bearer token forwarded to each layer's `run({ authToken })`. */\n authToken?: string;\n /**\n * Endpoints to skip — matched against `<page>/<name>` (version-agnostic) and\n * `<page>/<name>/<version>`, same convention as the built-in sweeps.\n */\n skip?: string[];\n /** Substring filter on `<page>/<name>/<version>`, same as the built-in sweeps. */\n filter?: string;\n /** Generated `apiInputSchemas` — source of each API route's `sampleInput`. */\n apiInputSchemas?: InputSchemaMap;\n /** Generated `syncInputSchemas` — source of each sync route's `sampleInput`. */\n syncInputSchemas?: InputSchemaMap;\n /** Custom sample factory; wins over the schema maps when given. */\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n}\n\n//? Whether a webhook URL targets a loopback host (localhost / 127.0.0.0/8 / ::1).\n//? Plaintext http to a non-loopback target leaks the route inventory (+ bearer)\n//? over the wire, so we warn — but never block, since the URL is consumer-self-\n//? registered.\nconst isLoopbackHost = (host: string): boolean =>\n host === 'localhost'\n || host === '::1'\n || host === '[::1]'\n || /^127\\.\\d+\\.\\d+\\.\\d+$/.test(host);\n\n//? POST the summary to the reporter's webhook, if one is configured. Best-effort\n//? — a webhook failure must never fail the test run.\nconst postWebhook = async (summary: TestSummary): Promise<void> => {\n const reporter = getTestReporter();\n const webhookUrl = reporter?.webhookUrl;\n if (!reporter || !webhookUrl) return;\n //? Warn (don't block) when forwarding the summary — which carries endpoint\n //? paths, error codes and reporter metadata verbatim — plaintext to a\n //? non-loopback host. Mirrors the plaintext-target caution the other network\n //? helpers carry.\n const [, parsedUrl] = tryCatchSync(() => new URL(webhookUrl));\n if (parsedUrl?.protocol === 'http:' && !isLoopbackHost(parsedUrl.hostname)) {\n console.warn(\n `[test-runner] reporter.webhookUrl is plaintext http to a non-loopback host (${parsedUrl.hostname}); `\n + 'the summary (endpoint paths, error codes, metadata) and any bearer token are sent unencrypted.',\n );\n }\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (reporter.webhookAuth?.type === 'bearer') headers.Authorization = `Bearer ${reporter.webhookAuth.token}`;\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, 10_000);\n await tryCatch(() => fetch(webhookUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify(summary),\n signal: controller.signal,\n }));\n clearTimeout(timeoutHandle);\n};\n\nexport const runRegisteredLayers = async (input: RunRegisteredLayersInput): Promise<TestSummary> => {\n const layers = listTestLayers();\n const endpoints = selectEndpoints(input);\n const reporter = getTestReporter();\n const skip = input.skip ?? [];\n const results: TestResult[] = [];\n //? Same sample source as the built-in sweeps, tagged with a run-unique marker\n //? so rows a layer creates are identifiable (finding #98).\n const inputFor = input.inputFor ?? createSampleInputFor({\n apiInputSchemas: input.apiInputSchemas,\n syncInputSchemas: input.syncInputSchemas,\n stringPrefix: createTestDataMarker(),\n });\n\n for (const layer of layers) {\n for (const endpoint of endpoints) {\n if (shouldSkip(endpoint, skip)) continue;\n const started = Date.now();\n const [runError, layerResult] = await tryCatch(async () =>\n layer.run({\n endpoint: endpoint.fullPath,\n method: endpoint.kind === 'sync' ? undefined : endpoint.method,\n authToken: input.authToken,\n sampleInput: inputFor(endpoint),\n }));\n const durationMs = Date.now() - started;\n\n const result: TestResult = runError || !layerResult\n ? {\n layer: layer.name,\n endpoint: endpoint.fullPath,\n passed: false,\n message: runError?.message ?? 'layer returned no result',\n durationMs,\n }\n : {\n layer: layer.name,\n endpoint: endpoint.fullPath,\n passed: layerResult.passed,\n message: layerResult.message,\n durationMs,\n metadata: layerResult.metadata,\n };\n\n results.push(result);\n await tryCatch(async () => reporter?.onResult?.(result));\n }\n }\n\n const summary: TestSummary = {\n totalLayers: layers.length,\n totalEndpoints: endpoints.length,\n passed: results.filter(r => r.passed).length,\n failed: results.filter(r => !r.passed).length,\n results,\n };\n\n await tryCatch(async () => reporter?.onSummary?.(summary));\n await postWebhook(summary);\n\n return summary;\n};\n"],"mappings":"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","names":["DEFAULT_REQUEST_TIMEOUT_MS","DEFAULT_REQUEST_TIMEOUT_MS","tryCatch","DEFAULT_REQUEST_TIMEOUT_MS","DEFAULT_REQUEST_TIMEOUT_MS","DEFAULT_REQUEST_TIMEOUT_MS","tryCatch","getProjectConfig","tryCatch","tryCatch","tryCatch","fs","path","buildRoutedGetUrl","getProjectConfig","tryCatch","socketEventNames","tryCatch","LABEL","socketEventNames","tryCatch","fs","path","tryCatch","buildRoutedGetUrl","getProjectConfig","crypto","fs","path","getCsrfConfig","getProjectConfig","tryCatch","tryCatchSync","getProjectConfig","getCsrfConfig","crypto","tryCatch","path","tryCatchSync","fs","tryCatch","tryCatchSync","reporter","tryCatchSync","tryCatch","layers"]}
|
|
1
|
+
{"version":3,"sources":["../src/walkEndpoints.ts","../src/probeRequest.ts","../src/testLayerHelpers.ts","../src/contractCheck.ts","../src/resetServerState.ts","../src/runContractTests.ts","../src/authEnforcementCheck.ts","../src/runAuthEnforcementTests.ts","../src/rateLimitCheck.ts","../src/runRateLimitTests.ts","../src/csrfEnforcementCheck.ts","../src/runCsrfEnforcementTests.ts","../src/fuzzCheck.ts","../src/runFuzzTests.ts","../src/socketProbeTransport.ts","../src/testSocket.ts","../src/schemaSampleInput.ts","../src/sampleInputFor.ts","../src/resolveTestBaseUrl.ts","../src/resolveTestEnvironment.ts","../src/testDataMarker.ts","../src/reporter.ts","../src/customTests.ts","../src/streamWatcher.ts","../src/runAllTests.ts","../src/extensionRegistry.ts","../src/runRegisteredLayers.ts"],"sourcesContent":["import type { EndpointDescriptor, HttpMethod, SyncMetaMap, SyncMethodMap } from './types';\n\ntype ApiMethodMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, string>>>>>>;\n\n//? Must stay in sync with the `HttpMethod` union in types.ts. Only values that\n//? appear in both sets are valid probes — anything else is an unrecognised method.\nconst VALID_HTTP_METHODS = new Set<HttpMethod>(['GET', 'POST', 'PUT', 'DELETE']);\n\nconst toHttpMethod = (raw: string): HttpMethod | null => {\n //? Guard against a stale or hand-edited generated map carrying an unrecognised\n //? method string. An unknown value would propagate silently as a typed HttpMethod\n //? and break every probe that uses it. Return null so the caller can skip it.\n const upper = raw.toUpperCase();\n for (const m of VALID_HTTP_METHODS) {\n if (m === upper) return m;\n }\n return null;\n};\n\nexport const walkEndpoints = (apiMethodMap: ApiMethodMap): EndpointDescriptor[] => {\n const endpoints: EndpointDescriptor[] = [];\n for (const [page, nameMap] of Object.entries(apiMethodMap)) {\n if (!nameMap) continue;\n for (const [name, versionMap] of Object.entries(nameMap)) {\n if (!versionMap) continue;\n for (const [version, rawMethod] of Object.entries(versionMap)) {\n if (rawMethod === undefined) continue;\n const method = toHttpMethod(rawMethod);\n if (!method) {\n //? Unrecognised method in the generated map — log and skip rather than\n //? silently coercing, which would produce invalid probes.\n console.warn(`[test-runner] walkEndpoints: skipping api/${page}/${name}/${version} — unrecognised method '${rawMethod}' in apiMethodMap`);\n continue;\n }\n endpoints.push({\n page,\n name,\n version,\n method,\n fullPath: `api/${page}/${name}/${version}`,\n });\n }\n }\n }\n return endpoints;\n};\n\n//? Walk a generated sync route map — the `syncMetaMap` devkit emits, or a\n//? `syncMethodMap`-shaped map — and produce a flat list of sync-route\n//? descriptors. Only the page → name → version keys matter; the leaf value is\n//? ignored. Each descriptor's `fullPath` is `sync/<page>/<name>/<version>` and\n//? its `kind` is `'sync'`. Sync routes always POST over the HTTP transport, so\n//? the method is hardcoded to `POST` here.\ntype SyncRouteMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, unknown>>>>>>;\n\nexport const walkSyncEndpoints = (syncMap: SyncMethodMap | SyncMetaMap | SyncRouteMap): EndpointDescriptor[] => {\n const endpoints: EndpointDescriptor[] = [];\n for (const [page, nameMap] of Object.entries(syncMap)) {\n if (!nameMap) continue;\n for (const [name, versionMap] of Object.entries(nameMap)) {\n if (!versionMap) continue;\n for (const [version, leaf] of Object.entries(versionMap)) {\n if (leaf === undefined) continue;\n endpoints.push({\n page,\n name,\n version,\n method: 'POST',\n fullPath: `sync/${page}/${name}/${version}`,\n kind: 'sync',\n });\n }\n }\n }\n return endpoints;\n};\n","//? Single-source probe used by every check layer (contract, auth, csrf,\n//? rate-limit, fuzz). Two levels:\n//? - `sendProbe` — one timed HTTP fetch + JSON-envelope parse. Centralises the\n//? AbortController + timeout + tryCatch pattern so it isn't copied into each\n//? check file.\n//? - `ProbeTransport` — \"send this payload to this endpoint\". The checks only\n//? know endpoints and payloads; the transport owns the wire: URL building,\n//? the GET query-string encoding, the sync `{ data, receiver }` envelope, and\n//? (socket transport, see `socketProbeTransport.ts`) Socket.io delivery.\n\nimport { buildRoutedGetUrl, tryCatch } from '@luckystack/core';\nimport type { EndpointDescriptor, HttpMethod } from './types';\n\nexport interface ProbeRequestInput {\n url: string;\n method: HttpMethod;\n baseUrl: string;\n body?: unknown;\n headers?: Record<string, string>;\n requestTimeoutMs: number;\n}\n\nexport interface ProbeResponse {\n /** HTTP status. Over Socket.io: the envelope's `httpStatus`, when it carries one. */\n httpStatus?: number;\n parsed: { status?: string; errorCode?: string } | null;\n}\n\n/**\n * Send a single timed fetch probe and parse the JSON envelope. Returns\n * `null` when the request itself fails (network error or abort).\n */\nexport const sendProbe = async (input: ProbeRequestInput): Promise<ProbeResponse | null> => {\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, input.requestTimeoutMs);\n\n //? A GET cannot carry a body (fetch throws), so its payload rides the query\n //? string as the reserved `__luckystack_data` value — the same encoding the\n //? real client sends and the server reads. Dropping it instead (the old\n //? behaviour) made every GET route with a required field fail validation.\n const isGet = input.method === 'GET';\n const url = isGet && input.body !== undefined ? buildRoutedGetUrl(input.url, input.body) : input.url;\n\n const [fetchError, response] = await tryCatch(() => fetch(url, {\n method: input.method,\n headers: {\n 'Content-Type': 'application/json',\n //? Browsers attach Origin on state-changing requests; the server's origin\n //? policy fail-closes POST/PUT/DELETE without one.\n 'Origin': new URL(input.baseUrl).origin,\n ...input.headers,\n },\n body: isGet ? undefined : JSON.stringify(input.body),\n signal: controller.signal,\n }));\n clearTimeout(timeoutHandle);\n\n if (fetchError || !response) return null;\n\n const [, parsed] = await tryCatch<{ status?: string; errorCode?: string } | null, undefined>(\n async () => await response.json() as { status?: string; errorCode?: string },\n );\n\n return { httpStatus: response.status, parsed: parsed ?? null };\n};\n\nexport interface ProbeTransportRequest {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n /** The route payload — the API `data`, or the sync `data` (the transport adds the sync envelope). */\n body: unknown;\n /** HTTP headers (session Cookie or Bearer, CSRF). A socket transport reads only the session. */\n headers?: Record<string, string>;\n requestTimeoutMs: number;\n}\n\nexport interface ProbeTransport {\n readonly name: 'http' | 'socket';\n /** Resolve with the parsed reply, or `null` when no reply arrived (network error / timeout). */\n send: (request: ProbeTransportRequest) => Promise<ProbeResponse | null>;\n /** Release held connections. Optional — the HTTP transport holds none. */\n close?: () => Promise<void>;\n}\n\nexport interface HttpProbeTransportOptions {\n /**\n * Room sent as `receiver` on sync probes. Without one the server answers\n * `sync.missingReceiver`, which proves the envelope but not the route.\n */\n syncReceiver?: string;\n}\n\n//? HTTP transport: API routes go to `/api/...` with the payload as the body (or\n//? query string for GET); sync routes go to `/sync/...` wrapped in the same\n//? `{ data, receiver }` envelope the routed-HTTP client sends.\nexport const createHttpProbeTransport = (options: HttpProbeTransportOptions = {}): ProbeTransport => ({\n name: 'http',\n send: (request) => sendProbe({\n url: `${request.baseUrl.replace(/\\/$/, '')}/${request.endpoint.fullPath}`,\n method: request.endpoint.method,\n baseUrl: request.baseUrl,\n body: request.endpoint.kind === 'sync'\n ? { data: request.body, receiver: options.syncReceiver }\n : request.body,\n headers: request.headers,\n requestTimeoutMs: request.requestTimeoutMs,\n }),\n});\n\n/** Transport the checks use when the caller supplies none. */\nexport const defaultProbeTransport: ProbeTransport = createHttpProbeTransport();\n","//? Shared helpers for the sweep test layers (contract, auth-enforcement,\n//? rate-limit, fuzz). Centralizes the endpoint selection, skip-matching,\n//? meta-map queries, and summary-count math that were previously copy-pasted\n//? across each layer file.\n\nimport { walkEndpoints, walkSyncEndpoints } from './walkEndpoints';\nimport { defaultProbeTransport } from './probeRequest';\nimport type { ProbeTransport } from './probeRequest';\nimport type {\n ApiMethodMap,\n ApiMetaMap,\n ContractCheckResult,\n EndpointDescriptor,\n HttpMethod,\n RouteMetaMap,\n RunContractSummary,\n SyncMetaMap,\n} from './types';\n\n//? Single source of truth for the canonical layer names used in both the\n//? orchestrator (`runAllTests`) and the reporter (`logRunAllSummary`). Keeping\n//? them here prevents silent drift between the reporting labels and the summary\n//? property keys when layers are added or renamed.\nexport const LAYER_KEYS = {\n contract: 'contract',\n auth: 'auth-enforcement',\n rateLimit: 'rate-limit',\n csrf: 'csrf-enforcement',\n fuzz: 'fuzz',\n custom: 'custom',\n} as const;\n\n//? HTTP methods that carry a body and mutate server state. The CSRF middleware\n//? enforces a token only on these. Fuzz / rate-limit layers share this set to\n//? avoid firing junk bodies at mutating authenticated routes in cookie-mode.\nexport const STATE_CHANGING_METHODS = new Set<HttpMethod>(['POST', 'PUT', 'DELETE']);\n\n/** `<page>/<name>/<version>` — the key `filter` and `skip` match against. */\nexport const routeKey = (endpoint: EndpointDescriptor): string =>\n `${endpoint.page}/${endpoint.name}/${endpoint.version}`;\n\n/** Whether an endpoint matches a substring `filter` (no filter matches all). */\nexport const matchesFilter = (endpoint: EndpointDescriptor, filter: string | undefined): boolean =>\n !filter || routeKey(endpoint).includes(filter);\n\nexport interface SelectEndpointsInput {\n apiMethodMap: ApiMethodMap;\n /** Generated `syncMetaMap`. When given, sync routes are swept too. */\n syncMetaMap?: SyncMetaMap;\n /** Substring filter on `<page>/<name>/<version>`. Non-matching routes are never requested. */\n filter?: string;\n}\n\n/**\n * The endpoints a sweep layer exercises: every API route, plus every sync\n * route when a `syncMetaMap` is supplied, narrowed by `filter`. Filtering\n * here — before any request — is what makes `filter` limit the traffic, not\n * just the report.\n */\nexport const selectEndpoints = (input: SelectEndpointsInput): EndpointDescriptor[] => [\n ...walkEndpoints(input.apiMethodMap),\n ...(input.syncMetaMap ? walkSyncEndpoints(input.syncMetaMap) : []),\n].filter((endpoint) => matchesFilter(endpoint, input.filter));\n\n/** The meta map that describes `endpoint`: `syncMetaMap` for sync routes, else `apiMetaMap`. */\nexport const metaMapFor = (\n endpoint: EndpointDescriptor,\n apiMetaMap: ApiMetaMap,\n syncMetaMap: SyncMetaMap | undefined,\n): RouteMetaMap => (endpoint.kind === 'sync' ? syncMetaMap ?? {} : apiMetaMap);\n\n/**\n * The transport for `endpoint`: `syncTransport` for sync routes and\n * `apiTransport` for API routes when given, else HTTP.\n */\nexport const transportFor = (\n endpoint: EndpointDescriptor,\n syncTransport: ProbeTransport | undefined,\n apiTransport?: ProbeTransport,\n): ProbeTransport => (endpoint.kind === 'sync' ? syncTransport : apiTransport) ?? defaultProbeTransport;\n\n/**\n * Whether sweep headers carry a session: a session Cookie (cookie mode) or an\n * `Authorization: Bearer` token (token mode). Layers that must not fire junk\n * at mutating routes treat both as an authenticated sweep.\n */\nexport const hasSessionHeaders = (headers: Record<string, string> | undefined): boolean =>\n Boolean(headers?.Cookie ?? headers?.Authorization);\n\n//? Codes a rate limiter answers with. Such a reply never reached input\n//? validation or the handler, so it proves nothing about the route.\nexport const isRateLimitedCode = (errorCode: string | undefined): boolean =>\n errorCode?.endsWith('.rateLimitExceeded') ?? false;\n\n/**\n * Whether an endpoint is in the explicit skip list. Matched against\n * `<page>/<name>` (version-agnostic) and `<page>/<name>/<version>`\n * (version-specific).\n */\nexport const shouldSkip = (endpoint: EndpointDescriptor, skip: string[]): boolean => {\n if (skip.length === 0) return false;\n const versioned = routeKey(endpoint);\n const versionless = `${endpoint.page}/${endpoint.name}`;\n return skip.includes(versioned) || skip.includes(versionless);\n};\n\n/** Whether the endpoint's meta entry declares `auth.login: true`. */\nexport const requiresLogin = (metaMap: RouteMetaMap, endpoint: EndpointDescriptor): boolean => {\n const meta = metaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version];\n //? The meta map is a consumer-generated RUNTIME artifact — the static type\n //? declares `auth` as required, but a stale/partial entry can be missing it.\n //? Read defensively so a malformed entry surfaces as `false` (no login) and\n //? gets handled by the layer's skip path, instead of throwing a TypeError\n //? outside the per-endpoint tryCatch and aborting the whole layer.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime-defensive read of a generated artifact\n return meta?.auth?.login ?? false;\n};\n\n/** Whether the endpoint requires ANY authorization: `auth.login: true` OR a\n * declared non-empty `auth.additional[]` predicate list (role/tenant/ownership\n * guards). The auth sweep must probe BOTH — an additional[]-only route (login:\n * false) is still authz-protected and an unauth call must be rejected. */\nexport const hasAuthRequirement = (metaMap: RouteMetaMap, endpoint: EndpointDescriptor): boolean => {\n const meta = metaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version];\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime-defensive read of a generated artifact\n return (meta?.auth?.login ?? false) || (meta?.auth?.hasAdditional ?? false);\n};\n\n/** Whether the meta map carries an entry for this endpoint at all. */\nexport const hasMetaEntry = (metaMap: RouteMetaMap, endpoint: EndpointDescriptor): boolean =>\n metaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version] !== undefined;\n\n/**\n * The endpoint's declared numeric rate limit, or `null` when it has none\n * (`false`/`undefined`) or no meta entry exists.\n */\nexport const getRateLimit = (metaMap: RouteMetaMap, endpoint: EndpointDescriptor): number | null => {\n const meta = metaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version];\n if (!meta || meta.rateLimit === false || meta.rateLimit === undefined) return null;\n return meta.rateLimit;\n};\n\n/** Aggregate pass/fail/skip counts over a sweep layer's results. */\nexport const calculateSummary = (results: ContractCheckResult[]): RunContractSummary => ({\n total: results.length,\n passed: results.filter(r => r.status === 'pass').length,\n failed: results.filter(r => r.status === 'fail').length,\n skipped: results.filter(r => r.status === 'skipped').length,\n results,\n});\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { defaultProbeTransport } from './probeRequest';\nimport type { ProbeTransport } from './probeRequest';\nimport { isRateLimitedCode } from './testLayerHelpers';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\n\nexport interface ContractCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n /**\n * Optional input factory. Defaults to `{}`. When an endpoint requires a\n * specific shape to not crash, callers can override per-endpoint here.\n */\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n /**\n * Optional headers (auth tokens, cookies). Applied to every request.\n */\n headers?: Record<string, string>;\n /**\n * Per-call request timeout in ms. Defaults to 5000. Bump for slow endpoints\n * (AI calls, large reports) where the framework default would false-fail\n * the contract check.\n */\n requestTimeoutMs?: number;\n /** How the probe reaches the server. Defaults to HTTP. */\n transport?: ProbeTransport;\n /**\n * The route is designed to fail on the server (e.g. a playground route that\n * throws on purpose): a shaped 5xx / execution-failed reply then passes.\n * Routing errors (unknown route, wrong method) still fail.\n */\n allowServerError?: boolean;\n}\n\n//? Reply codes that mean the probe never reached a handler for this route: the\n//? generated route map and the live server disagree (stale map, route missing\n//? on the server, wrong method). A shaped envelope does not make that a pass.\nconst ROUTING_ERROR_CODES = new Set([\n 'api.notFound',\n 'sync.notFound',\n 'api.methodNotAllowed',\n 'sync.methodNotAllowed',\n 'api.invalidName',\n 'sync.invalidName',\n]);\n\n//? Codes the pipelines answer with when the handler crashed. Socket replies\n//? may lack an HTTP status, so the code is checked alongside `httpStatus >= 500`.\nconst SERVER_ERROR_CODES = new Set([\n 'api.serverExecutionFailed',\n 'sync.serverExecutionFailed',\n 'api.internalServerError',\n 'sync.internalServerError',\n]);\n\n//? Contract layer: send any-shape input, accept any response where\n//? `status` is `success` or `error` (with an `errorCode`). Anything else is\n//? a contract violation — 5xx, thrown exceptions, routing errors, or shapes\n//? outside this envelope.\n//?\n//? The goal here is NOT to prove every endpoint works end-to-end; it's to\n//? catch endpoints that crash the server or return garbage on well-formed\n//? empty input. Real behavior tests go in the per-endpoint test file.\nexport const runContractCheck = async (input: ContractCheckInput): Promise<ContractCheckResult> => {\n const { endpoint, baseUrl } = input;\n const body = input.inputFor ? input.inputFor(endpoint) : {};\n const started = Date.now();\n\n const result = await (input.transport ?? defaultProbeTransport).send({\n endpoint,\n baseUrl,\n body,\n headers: input.headers,\n requestTimeoutMs: input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n });\n\n if (!result) {\n return {\n endpoint,\n status: 'fail',\n reason: 'fetch returned no response',\n durationMs: Date.now() - started,\n };\n }\n\n const { httpStatus, parsed } = result;\n const durationMs = Date.now() - started;\n\n if (!parsed) {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'unknown',\n reason: 'JSON parse failed',\n durationMs,\n };\n }\n\n if (parsed.status !== 'success' && parsed.status !== 'error') {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'unknown',\n reason: 'Response missing standard `status` envelope',\n durationMs,\n };\n }\n\n if (parsed.status === 'error' && !parsed.errorCode) {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'error',\n reason: 'Error response missing `errorCode`',\n durationMs,\n };\n }\n\n //? The limiter answers before input validation, so the route was not\n //? exercised at all — neither a pass nor a contract failure.\n if (isRateLimitedCode(parsed.errorCode)) {\n return {\n endpoint,\n status: 'skipped',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n reason: 'rate-limited before the route ran (not exercised); pass a reset token so the sweep resets rate limits between routes',\n durationMs,\n };\n }\n\n if (parsed.errorCode !== undefined && ROUTING_ERROR_CODES.has(parsed.errorCode)) {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n reason: 'route not reachable as generated (routing error) — the generated route map and the live server disagree',\n durationMs,\n };\n }\n\n const isServerError = (httpStatus !== undefined && httpStatus >= 500)\n || (parsed.errorCode !== undefined && SERVER_ERROR_CODES.has(parsed.errorCode));\n if (isServerError) {\n return {\n endpoint,\n status: input.allowServerError ? 'pass' : 'fail',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n reason: input.allowServerError\n ? 'expected server error (listed in expectServerError)'\n : 'server error on a well-formed probe — list the route in expectServerError if it is designed to fail',\n durationMs,\n };\n }\n\n //? When `inputFor` produced a non-empty sample body but the route returned an\n //? error, the probe may have failed its own Zod validation rather than proving\n //? the contract. Counting that as a PASS is misleading — the happy-path was\n //? never exercised. Classify as `skipped` so the caller knows the body was\n //? rejected before the handler ran and can supply a better sample.\n const bodyIsNonEmpty = body !== null\n && typeof body === 'object'\n && !Array.isArray(body)\n && Object.keys(body).length > 0;\n if (input.inputFor && bodyIsNonEmpty && parsed.status === 'error') {\n return {\n endpoint,\n status: 'skipped',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n reason: `inputFor produced a non-empty sample but route returned error (${parsed.errorCode ?? 'unknown'}); provide a valid sample via inputFor to test the happy path`,\n durationMs,\n };\n }\n\n return {\n endpoint,\n status: 'pass',\n httpStatus,\n responseStatus: parsed.status,\n errorCode: parsed.errorCode,\n durationMs,\n };\n};\n","import { getProjectConfig, TEST_RESET_ONLY_PARAM, tryCatch } from '@luckystack/core';\nimport type { TestResetTarget } from '@luckystack/core';\n\n//? Calls the server's /_test/reset endpoint. Used between rate-limit test\n//? endpoints to drain the shared IP-based limiter bucket so one endpoint's\n//? N+1 assertion doesn't consume the next endpoint's window.\n//?\n//? /_test/reset is gated by `NODE_ENV in { 'development', 'test' }` on the\n//? server side AND requires `TEST_RESET_TOKEN` to be set unconditionally\n//? (no token = 403). For staging/preview deploys that enable it for CI,\n//? set `TEST_RESET_TOKEN` on the server and pass the same value here.\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 10_000;\n\nexport interface ResetServerStateInput {\n baseUrl: string;\n token?: string;\n requestTimeoutMs?: number;\n /**\n * Clear only these targets (`?only=`). Omit for the server's default reset,\n * which ALSO deletes every Redis session — including an authenticated sweep's\n * own. The runner itself always passes `['rateLimits']`.\n */\n only?: TestResetTarget[];\n}\n\nexport const resetServerState = async ({ baseUrl, token, requestTimeoutMs, only }: ResetServerStateInput): Promise<boolean> => {\n const endpoint = `${baseUrl.replace(/\\/$/, '')}${getProjectConfig().http.testResetEndpoint}`;\n const url = only && only.length > 0\n ? `${endpoint}?${new URLSearchParams({ [TEST_RESET_ONLY_PARAM]: only.join(',') }).toString()}`\n : endpoint;\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (token) headers['X-Test-Reset-Token'] = token;\n\n //? Bound the call so a server that accepts the connection but never responds\n //? returns `false` (the existing failure path) instead of wedging the sweep.\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS);\n const [error, response] = await tryCatch(() => fetch(url, { method: 'POST', headers, signal: controller.signal }));\n clearTimeout(timeoutHandle);\n if (error || !response) return false;\n return response.ok;\n};\n","import { runContractCheck } from './contractCheck';\nimport { resetServerState } from './resetServerState';\nimport { shouldSkip, calculateSummary, selectEndpoints, transportFor } from './testLayerHelpers';\nimport type { ProbeTransport } from './probeRequest';\nimport type { ApiMethodMap, ContractCheckResult, EndpointDescriptor, RunContractSummary, SyncMetaMap } from './types';\n\nexport interface RunContractTestsInput {\n apiMethodMap: ApiMethodMap;\n /** Generated `syncMetaMap`. When given, sync routes are swept too. */\n syncMetaMap?: SyncMetaMap;\n baseUrl: string;\n /**\n * Endpoints to skip. Matched against `<page>/<name>` (version-agnostic) and\n * `<page>/<name>/<version>` (version-specific). Useful for endpoints that\n * are known to need real input (e.g. a file upload).\n */\n skip?: string[];\n /**\n * Routes designed to fail on the server (e.g. a playground route that throws\n * on purpose), matched like `skip`. Their shaped 5xx reply passes; every\n * other route's 5xx fails the layer.\n */\n expectServerError?: string[];\n /** Substring filter on `<page>/<name>/<version>`; other routes are not requested at all. */\n filter?: string;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n headers?: Record<string, string>;\n /** Transport for sync routes (e.g. the socket transport). Defaults to HTTP. */\n syncTransport?: ProbeTransport;\n /** Transport for API routes (e.g. the socket transport). Defaults to HTTP. */\n apiTransport?: ProbeTransport;\n /**\n * When given, rate limits are reset (`/_test/reset?only=rateLimits`) before\n * each route, so the shared per-IP bucket cannot fill up mid-sweep and turn\n * the remaining probes into limiter rejections.\n */\n resetToken?: string;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runContractTests = async (input: RunContractTestsInput): Promise<RunContractSummary> => {\n const endpoints = selectEndpoints(input);\n const skip = input.skip ?? [];\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (input.resetToken) {\n await resetServerState({ baseUrl: input.baseUrl, token: input.resetToken, only: ['rateLimits'] });\n }\n\n //? Serial rather than parallel so we don't trip rate limiters or mask\n //? endpoint ordering bugs. Swap to a pool later if walk-time matters.\n const result = await runContractCheck({\n endpoint,\n baseUrl: input.baseUrl,\n inputFor: input.inputFor,\n headers: input.headers,\n transport: transportFor(endpoint, input.syncTransport, input.apiTransport),\n allowServerError: shouldSkip(endpoint, input.expectServerError ?? []),\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { defaultProbeTransport } from './probeRequest';\nimport type { ProbeTransport } from './probeRequest';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\n\n//? Canonical error codes the framework emits when an unauthenticated request hits\n//? a protected endpoint: `auth.required` for an `auth.login: true` route, and\n//? `auth.forbidden` for a route guarded ONLY by `auth.additional[]` predicates\n//? (login: false) — validateRequest forbids the anonymous caller. Either is a PASS;\n//? both mean the guard fired. See packages/api/src/handleHttpApiRequest.ts +\n//? packages/core/src/validateRequest.ts.\nconst EXPECTED_ERROR_CODES = new Set(['auth.required', 'auth.forbidden']);\n\nexport interface AuthEnforcementCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n /** Per-call request timeout in ms. Defaults to 5000. */\n requestTimeoutMs?: number;\n /** How the probe reaches the server. Defaults to HTTP. */\n transport?: ProbeTransport;\n}\n\n//? Auth-enforcement layer: for each `auth.login: true` endpoint, call it with\n//? NO session headers/cookies. Expect the server to reject with `auth.required`.\n//?\n//? A passing endpoint here proves the login guard isn't silently bypassed.\n//? Endpoints that return `success` without a session are a critical finding.\nexport const runAuthEnforcementCheck = async (\n input: AuthEnforcementCheckInput,\n): Promise<ContractCheckResult> => {\n const { endpoint, baseUrl } = input;\n const body = input.inputFor ? input.inputFor(endpoint) : {};\n const started = Date.now();\n\n //? No session cookie, no auth header — deliberately (over a socket transport:\n //? an anonymous socket). Origin IS sent so the server's origin policy doesn't\n //? 403 before the auth check can run.\n const result = await (input.transport ?? defaultProbeTransport).send({\n endpoint,\n baseUrl,\n body,\n requestTimeoutMs: input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n });\n\n if (!result) {\n return {\n endpoint,\n status: 'fail',\n reason: 'fetch returned no response',\n durationMs: Date.now() - started,\n };\n }\n\n const { httpStatus, parsed } = result;\n const durationMs = Date.now() - started;\n\n if (parsed?.status === 'success') {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'success',\n reason: 'auth.login endpoint returned success without a session',\n durationMs,\n };\n }\n\n if (parsed?.status !== 'error') {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'unknown',\n reason: 'Response missing standard `status` envelope',\n durationMs,\n };\n }\n\n if (parsed.errorCode === undefined || !EXPECTED_ERROR_CODES.has(parsed.errorCode)) {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n reason: `expected an auth-rejection errorCode (${[...EXPECTED_ERROR_CODES].join(' | ')}) but got '${parsed.errorCode ?? '(missing)'}'`,\n durationMs,\n };\n }\n\n return {\n endpoint,\n status: 'pass',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n durationMs,\n };\n};\n","import { runAuthEnforcementCheck } from './authEnforcementCheck';\nimport {\n shouldSkip,\n hasAuthRequirement,\n hasMetaEntry,\n calculateSummary,\n metaMapFor,\n selectEndpoints,\n transportFor,\n} from './testLayerHelpers';\nimport type { ProbeTransport } from './probeRequest';\nimport type {\n ApiMethodMap,\n ApiMetaMap,\n ContractCheckResult,\n EndpointDescriptor,\n RunContractSummary,\n SyncMetaMap,\n} from './types';\n\nexport interface RunAuthEnforcementTestsInput {\n apiMethodMap: ApiMethodMap;\n apiMetaMap: ApiMetaMap;\n /** Generated `syncMetaMap`. When given, sync routes are swept too. */\n syncMetaMap?: SyncMetaMap;\n baseUrl: string;\n skip?: string[];\n /** Substring filter on `<page>/<name>/<version>`; other routes are not requested at all. */\n filter?: string;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n /** Transport for sync routes (e.g. the socket transport). Defaults to HTTP. */\n syncTransport?: ProbeTransport;\n /** Transport for API routes (e.g. the socket transport). Defaults to HTTP. */\n apiTransport?: ProbeTransport;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runAuthEnforcementTests = async (\n input: RunAuthEnforcementTestsInput,\n): Promise<RunContractSummary> => {\n const endpoints = selectEndpoints(input);\n const skip = input.skip ?? [];\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n const metaMap = metaMapFor(endpoint, input.apiMetaMap, input.syncMetaMap);\n //? Order matters: the auth-requirement check must run BEFORE shouldSkip so a\n //? protected endpoint in the explicit skip list is still recorded as\n //? `skipped` in the results (not silently dropped as a public route).\n //? Reversing the order would hide the skip from the summary. We gate on\n //? `hasAuthRequirement` (login OR additional[]-predicates) so role/tenant/\n //? ownership-guarded routes with `login: false` are also probed.\n if (!hasMetaEntry(metaMap, endpoint)) {\n //? Route is in the method/sync map but ABSENT from its meta map (partial\n //? drift / hand-trimmed map / generator bug). Its auth requirement is\n //? unknowable, so this layer can't assert it. Record a `skipped` result (NOT\n //? a silent continue) so the gap is visible per-route in the summary instead\n //? of the route masquerading as a verified-public endpoint. (M8)\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: `No ${endpoint.kind === 'sync' ? 'syncMetaMap' : 'apiMetaMap'} entry — auth requirement unverifiable`,\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (!hasAuthRequirement(metaMap, endpoint)) {\n //? Truly-public endpoints (meta entry present, login:false, no additional[])\n //? can't be tested by this layer — skip silently, no noise. They're still\n //? covered by the contract layer.\n continue;\n }\n\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n const result = await runAuthEnforcementCheck({\n endpoint,\n baseUrl: input.baseUrl,\n inputFor: input.inputFor,\n transport: transportFor(endpoint, input.syncTransport, input.apiTransport),\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { defaultProbeTransport } from './probeRequest';\nimport type { ProbeTransport } from './probeRequest';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 10_000;\n\n//? The limiter's rejection code is namespaced by route kind: `api.*` from the\n//? API pipeline, `sync.*` from both sync pipelines (socket + HTTP).\nconst expectedErrorCodeFor = (endpoint: EndpointDescriptor): string =>\n endpoint.kind === 'sync' ? 'sync.rateLimitExceeded' : 'api.rateLimitExceeded';\n\n//? Error codes the server emits BEFORE the rate-limit pipeline runs (auth guard\n//? + server-level CSRF middleware). When a drain response carries one of these,\n//? the bucket was never incremented — the final probe will fail for the wrong\n//? reason and should be classified as `skipped` rather than a real failure.\n//? These MUST match the framework's actual pre-limiter codes (pipeline order:\n//? auth → rateLimit, CSRF middleware before the limiter):\n//? - `auth.required` — login:true route called without a session\n//? (handleHttpApiRequest.ts).\n//? - `auth.forbidden` — additional[]-predicate failed / login:false guard\n//? (handleHttpApiRequest.ts).\n//? - `auth.csrfMismatch` — cookie-mode state-changing route, CSRF gate\n//? (server/httpRoutes/csrfMiddleware.ts).\n//? - `sync.notRoomMember` / `sync.receiverNotAllowed` / `sync.missingReceiver`\n//? — sync receiver checks, which run after auth and before the limiter\n//? (handleSyncRequest.ts / handleHttpSyncRequest.ts).\n//? Validation (`api.invalidInputType`) runs AFTER the limiter, so it is NOT a\n//? pre-limiter code and is deliberately excluded.\nconst PRE_LIMITER_ERROR_CODES = new Set([\n 'auth.required',\n 'auth.forbidden',\n 'auth.csrfMismatch',\n 'sync.notRoomMember',\n 'sync.receiverNotAllowed',\n 'sync.missingReceiver',\n]);\n\nexport interface RateLimitCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n rateLimit: number;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n headers?: Record<string, string>;\n /** Per-call request timeout in ms. Defaults to 10000. */\n requestTimeoutMs?: number;\n /** How the probes reach the server. Defaults to HTTP. */\n transport?: ProbeTransport;\n}\n\n//? Rate-limit layer: fire `rateLimit + 1` requests back to back and assert the\n//? last one is rejected with `api.rateLimitExceeded` (`sync.rateLimitExceeded`\n//? for a sync route). All preceding requests are allowed to return either\n//? success or any other envelope — only the N+1 matters for this layer.\n//?\n//? This layer mutates server-side state (limiter counters). Run it after the\n//? contract + auth layers, or against a dedicated test instance. The server's\n//? clearAllRateLimits() utility can reset state between runs.\nexport const runRateLimitCheck = async (input: RateLimitCheckInput): Promise<ContractCheckResult> => {\n const { endpoint, baseUrl, rateLimit } = input;\n const body = input.inputFor ? input.inputFor(endpoint) : {};\n const started = Date.now();\n const requestTimeoutMs = input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const expectedErrorCode = expectedErrorCodeFor(endpoint);\n const transport = input.transport ?? defaultProbeTransport;\n\n const send = () => transport.send({\n endpoint,\n baseUrl,\n body,\n headers: input.headers,\n requestTimeoutMs,\n });\n\n //? Fire `rateLimit` allowed requests first (drain the bucket), then a final\n //? request that should be blocked. Serial so the server sees a clean order.\n //? Inspect EVERY drain response for pre-limiter rejections (CSRF/auth/\n //? validation): if the server rejects before the limiter runs at any point,\n //? the bucket was never incremented for that request, the final probe will\n //? fail for the wrong reason, and we classify the check as `skipped`.\n for (let i = 0; i < rateLimit; i += 1) {\n const drained = await send();\n if (!drained) continue;\n const errorCode = drained.parsed?.errorCode;\n if (errorCode && PRE_LIMITER_ERROR_CODES.has(errorCode)) {\n return {\n endpoint,\n status: 'skipped',\n durationMs: Date.now() - started,\n reason: `drain request ${i + 1} rejected pre-limiter (${errorCode}); bucket was never incremented — check CSRF/auth headers`,\n };\n }\n }\n\n const final = await send();\n const durationMs = Date.now() - started;\n\n if (!final) {\n return {\n endpoint,\n status: 'fail',\n reason: 'Final rate-limit probe request failed to return a response',\n durationMs,\n };\n }\n\n const { httpStatus, parsed } = final;\n\n if (parsed?.status === 'error' && parsed.errorCode === expectedErrorCode) {\n return {\n endpoint,\n status: 'pass',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n durationMs,\n };\n }\n\n const responseStatus: ContractCheckResult['responseStatus'] =\n parsed?.status === 'success' || parsed?.status === 'error' ? parsed.status : 'unknown';\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus,\n errorCode: parsed?.errorCode,\n reason: `expected '${expectedErrorCode}' on request ${rateLimit + 1} but got ${parsed?.status ?? 'none'}/${parsed?.errorCode ?? '(none)'}`,\n durationMs,\n };\n};\n","import { runRateLimitCheck } from './rateLimitCheck';\nimport { resetServerState } from './resetServerState';\nimport {\n shouldSkip,\n requiresLogin,\n getRateLimit,\n calculateSummary,\n hasSessionHeaders,\n metaMapFor,\n selectEndpoints,\n transportFor,\n STATE_CHANGING_METHODS,\n} from './testLayerHelpers';\nimport type { ProbeTransport } from './probeRequest';\nimport type {\n ApiMethodMap,\n ApiMetaMap,\n ContractCheckResult,\n EndpointDescriptor,\n RunContractSummary,\n SyncMetaMap,\n} from './types';\n\nexport interface RunRateLimitTestsInput {\n apiMethodMap: ApiMethodMap;\n apiMetaMap: ApiMetaMap;\n /** Generated `syncMetaMap`. When given, sync routes are swept too. */\n syncMetaMap?: SyncMetaMap;\n baseUrl: string;\n skip?: string[];\n /** Substring filter on `<page>/<name>/<version>`; other routes are not requested at all. */\n filter?: string;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n headers?: Record<string, string>;\n /** Transport for sync routes (e.g. the socket transport). Defaults to HTTP. */\n syncTransport?: ProbeTransport;\n /** Transport for API routes (e.g. the socket transport). Defaults to HTTP. */\n apiTransport?: ProbeTransport;\n /**\n * Max rateLimit value to test. Endpoints with a higher limit are skipped\n * to avoid firing thousands of requests in CI.\n */\n maxRateLimitToTest?: number;\n /**\n * When true, hit `/_test/reset?only=rateLimits` before each endpoint so\n * limiter state is clean. Only the limiter buckets are cleared — sessions\n * survive, so an authenticated sweep keeps its session. Requires the server\n * to allow the endpoint: `NODE_ENV` in { 'development', 'test' } AND\n * `TEST_RESET_TOKEN` set on the server with a matching `resetToken` here (an\n * unset token is 403, never open). Without resetting, the shared IP bucket\n * leaks state between endpoints.\n */\n resetBetweenEndpoints?: boolean;\n /**\n * Token forwarded as `X-Test-Reset-Token` when resetting. Only relevant in\n * staging/preview deploys that enable the endpoint behind a token.\n */\n resetToken?: string;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runRateLimitTests = async (\n input: RunRateLimitTestsInput,\n): Promise<RunContractSummary> => {\n const endpoints = selectEndpoints(input);\n const skip = input.skip ?? [];\n const maxRateLimit = input.maxRateLimitToTest ?? 50;\n //? A login-required route returns `auth.required` BEFORE the rate limiter\n //? runs, so it can only be rate-limit-tested with a session. A session Cookie\n //? or Bearer header means an authenticated sweep. We deliberately do NOT\n //? auto-authenticate the sweep — that would execute real mutations\n //? (delete/update) with junk input. Set TEST_AUTH_TOKEN to opt in and cover\n //? login-gated routes.\n const isAuthenticatedSweep = hasSessionHeaders(input.headers);\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n const metaMap = metaMapFor(endpoint, input.apiMetaMap, input.syncMetaMap);\n const rateLimit = getRateLimit(metaMap, endpoint);\n if (rateLimit === null) continue;\n\n if (!isAuthenticatedSweep && requiresLogin(metaMap, endpoint)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'login-required route — set TEST_AUTH_TOKEN to rate-limit-test it (unauthenticated calls hit auth.required before the limiter)',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n //? In an authenticated sweep the rate-limit check fires the endpoint `rateLimit + 1`\n //? times with junk input. State-changing routes (POST/PUT/DELETE — every\n //? sync route) execute real mutation handlers on each hit — skip them to\n //? avoid side-effects on the test database. Their rate limits are better\n //? covered by a custom test that supplies valid input.\n if (isAuthenticatedSweep && STATE_CHANGING_METHODS.has(endpoint.method)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'state-changing route skipped in authenticated rate-limit sweep (mutation risk with junk bodies)',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (rateLimit > maxRateLimit) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: `rateLimit ${rateLimit} exceeds maxRateLimitToTest=${maxRateLimit}`,\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (input.resetBetweenEndpoints) {\n //? Limiter buckets only: a full reset also deletes every Redis session,\n //? including the one an authenticated sweep is running on.\n await resetServerState({ baseUrl: input.baseUrl, token: input.resetToken, only: ['rateLimits'] });\n }\n\n const result = await runRateLimitCheck({\n endpoint,\n baseUrl: input.baseUrl,\n rateLimit,\n inputFor: input.inputFor,\n headers: input.headers,\n transport: transportFor(endpoint, input.syncTransport, input.apiTransport),\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { defaultProbeTransport } from './probeRequest';\nimport type { ProbeTransport } from './probeRequest';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\n\n//? Canonical error code the framework's CSRF middleware emits when a\n//? state-changing, cookie-authenticated request arrives without a matching\n//? CSRF token. See packages/server/src/httpRoutes/csrfMiddleware.ts.\nconst DEFAULT_EXPECTED_ERROR_CODE = 'auth.csrfMismatch';\nconst DEFAULT_EXPECTED_HTTP_STATUS = 403;\n\nexport interface CsrfEnforcementCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n /**\n * A valid session Cookie header (`<name>=<token>`). The probe deliberately\n * carries it so the request passes the auth guard and reaches the CSRF check.\n */\n authCookie: string;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n /** Per-call request timeout in ms. Defaults to 5000. */\n requestTimeoutMs?: number;\n /** Error code expected on CSRF rejection. Defaults to `auth.csrfMismatch`. */\n expectedErrorCode?: string;\n /** HTTP status expected on CSRF rejection. Defaults to 403; pass `false` to skip. */\n expectedHttpStatus?: number | false;\n /**\n * HTTP transport the probe uses (e.g. one carrying the sync `receiver`).\n * CSRF is an HTTP-only guard, so a non-HTTP transport makes the check skip.\n * Defaults to plain HTTP.\n */\n transport?: ProbeTransport;\n}\n\n//? CSRF-enforcement layer: for each `auth.login: true` state-changing route,\n//? send an authenticated request (valid session Cookie) WITHOUT the CSRF\n//? header. Expect the framework's CSRF middleware to reject it. This is the\n//? inverse of the custom layer's CSRF token-passing — it proves the protection\n//? actually fires, so a consumer who misconfigures CSRF doesn't get a green run.\nexport const runCsrfEnforcementCheck = async (\n input: CsrfEnforcementCheckInput,\n): Promise<ContractCheckResult> => {\n const { endpoint, baseUrl } = input;\n //? CSRF is only enforced on state-changing methods (POST/PUT/DELETE). A GET\n //? probe would never reach the CSRF middleware regardless of headers, so it\n //? would always appear to pass — a false green. Guard here so the check is\n //? safe to call directly (the runner already filters, but direct callers may not).\n if (endpoint.method === 'GET') {\n return {\n endpoint,\n status: 'skipped',\n reason: `CSRF enforcement does not apply to ${endpoint.method} requests`,\n durationMs: 0,\n };\n }\n const transport = input.transport ?? defaultProbeTransport;\n if (transport.name !== 'http') {\n return {\n endpoint,\n status: 'skipped',\n reason: `CSRF enforcement only applies over HTTP (transport: ${transport.name})`,\n durationMs: 0,\n };\n }\n const body = input.inputFor ? input.inputFor(endpoint) : {};\n const started = Date.now();\n const expectedErrorCode = input.expectedErrorCode ?? DEFAULT_EXPECTED_ERROR_CODE;\n const expectedHttpStatus = input.expectedHttpStatus ?? DEFAULT_EXPECTED_HTTP_STATUS;\n\n //? Valid session Cookie + Origin, but NO CSRF header — that omission is the\n //? whole point of the probe.\n const result = await transport.send({\n endpoint,\n baseUrl,\n body,\n headers: { Cookie: input.authCookie },\n requestTimeoutMs: input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n });\n\n if (!result) {\n return { endpoint, status: 'fail', reason: 'fetch returned no response', durationMs: Date.now() - started };\n }\n\n const { httpStatus, parsed } = result;\n const durationMs = Date.now() - started;\n\n if (parsed?.status === 'success') {\n return {\n endpoint, status: 'fail', httpStatus, responseStatus: 'success',\n reason: 'state-changing endpoint accepted an authenticated request with NO CSRF header — CSRF protection is not enforced',\n durationMs,\n };\n }\n\n if (parsed?.status !== 'error' || parsed.errorCode !== expectedErrorCode) {\n const responseStatus: ContractCheckResult['responseStatus'] =\n parsed?.status === 'success' || parsed?.status === 'error' ? parsed.status : 'unknown';\n return {\n endpoint, status: 'fail', httpStatus, responseStatus, errorCode: parsed?.errorCode,\n reason: `expected CSRF rejection '${expectedErrorCode}' but got ${parsed?.status ?? 'none'}/${parsed?.errorCode ?? '(none)'}`,\n durationMs,\n };\n }\n\n if (expectedHttpStatus !== false && httpStatus !== expectedHttpStatus) {\n return {\n endpoint, status: 'fail', httpStatus, responseStatus: 'error', errorCode: parsed.errorCode,\n reason: `expected HTTP ${expectedHttpStatus} on CSRF rejection but got ${httpStatus}`,\n durationMs,\n };\n }\n\n return { endpoint, status: 'pass', httpStatus, responseStatus: 'error', errorCode: parsed.errorCode, durationMs };\n};\n","import { runCsrfEnforcementCheck } from './csrfEnforcementCheck';\nimport {\n shouldSkip,\n requiresLogin,\n hasMetaEntry,\n calculateSummary,\n metaMapFor,\n selectEndpoints,\n transportFor,\n STATE_CHANGING_METHODS,\n} from './testLayerHelpers';\nimport type { ProbeTransport } from './probeRequest';\nimport type {\n ApiMethodMap,\n ApiMetaMap,\n ContractCheckResult,\n EndpointDescriptor,\n RunContractSummary,\n SyncMetaMap,\n} from './types';\n\nexport interface RunCsrfEnforcementTestsInput {\n apiMethodMap: ApiMethodMap;\n apiMetaMap: ApiMetaMap;\n /** Generated `syncMetaMap`. When given, sync routes' `/sync/*` HTTP endpoints are probed too. */\n syncMetaMap?: SyncMetaMap;\n baseUrl: string;\n /** Valid session Cookie header (`<name>=<token>`) the probes carry so they reach the CSRF check. */\n authCookie: string;\n skip?: string[];\n /** Substring filter on `<page>/<name>/<version>`; other routes are not requested at all. */\n filter?: string;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n /**\n * HTTP transport for sync routes (one carrying the sync `receiver`). CSRF is\n * an HTTP-only guard, so this must be an HTTP transport even when the project\n * syncs over Socket.io — the `/sync/*` HTTP route is reachable either way.\n */\n syncTransport?: ProbeTransport;\n /** Per-call request timeout in ms forwarded to each check. */\n requestTimeoutMs?: number;\n /** Error code expected on CSRF rejection. Defaults to `auth.csrfMismatch`. */\n expectedErrorCode?: string;\n /** HTTP status expected on CSRF rejection. Defaults to 403; pass `false` to skip. */\n expectedHttpStatus?: number | false;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runCsrfEnforcementTests = async (\n input: RunCsrfEnforcementTestsInput,\n): Promise<RunContractSummary> => {\n const endpoints = selectEndpoints(input);\n const skip = input.skip ?? [];\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n const metaMap = metaMapFor(endpoint, input.apiMetaMap, input.syncMetaMap);\n //? Only login-required, state-changing routes are CSRF-protected. A missing\n //? meta entry means we can't know the auth requirement — skip silently here\n //? (the auth layer already surfaces the stale-map gap).\n if (!hasMetaEntry(metaMap, endpoint)) continue;\n if (!requiresLogin(metaMap, endpoint)) continue;\n if (!STATE_CHANGING_METHODS.has(endpoint.method)) continue;\n\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n const result = await runCsrfEnforcementCheck({\n endpoint,\n baseUrl: input.baseUrl,\n authCookie: input.authCookie,\n inputFor: input.inputFor,\n requestTimeoutMs: input.requestTimeoutMs,\n expectedErrorCode: input.expectedErrorCode,\n expectedHttpStatus: input.expectedHttpStatus,\n transport: transportFor(endpoint, input.syncTransport),\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { defaultProbeTransport } from './probeRequest';\nimport type { ProbeTransport } from './probeRequest';\nimport { isRateLimitedCode } from './testLayerHelpers';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\n\n//? Lightweight fuzz layer — no Zod yet. Sends well-known junk shapes and\n//? asserts the server doesn't 5xx or return a non-envelope body. Real\n//? schema-driven fuzzing (TS type -> random valid input) is deferred until\n//? the generator emits Zod schemas alongside the types.\nconst JUNK_PAYLOADS: readonly unknown[] = [\n null,\n [],\n [1, 2, 3],\n 'string-instead-of-object',\n 1_234_567_890,\n true,\n { nested: { deeply: { nested: { value: 'x'.repeat(10_000) } } } },\n //? `{ __proto__: ... }` serializes to `{}` via JSON.stringify (prototype keys\n //? are not own-enumerable), so it would probe nothing. Use `constructor` instead\n //? — a named own-key that some parsers misinterpret but survives JSON round-trip.\n { constructor: { polluted: true } },\n { key: null, other: undefined, third: Number.NaN },\n];\n\nexport interface FuzzCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n headers?: Record<string, string>;\n /** Per-call request timeout in ms. Defaults to 5000. */\n requestTimeoutMs?: number;\n /** How the probes reach the server. Defaults to HTTP. */\n transport?: ProbeTransport;\n}\n\nexport const runFuzzCheck = async (input: FuzzCheckInput): Promise<ContractCheckResult> => {\n const started = Date.now();\n const requestTimeoutMs = input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const transport = input.transport ?? defaultProbeTransport;\n\n for (const [index, payload] of JUNK_PAYLOADS.entries()) {\n const result = await transport.send({\n endpoint: input.endpoint,\n baseUrl: input.baseUrl,\n body: payload,\n headers: input.headers,\n requestTimeoutMs,\n });\n const durationMs = Date.now() - started;\n\n if (!result) {\n return {\n endpoint: input.endpoint,\n status: 'fail',\n reason: `fuzz probe crashed (no response) with payload: ${JSON.stringify(payload).slice(0, 80)}`,\n durationMs,\n };\n }\n\n //? A 5xx that STILL returns a valid `{ status:'error', errorCode }` envelope\n //? is the framework gracefully catching an error (e.g. a route designed to\n //? throw), not a crash. The fuzz layer's concern is crashes / hangs /\n //? garbage — so we only fail on a non-envelope body below (which catches raw\n //? 5xx, HTML error pages, and truncated responses). A controlled 500\n //? envelope passes.\n if (!result.parsed || (result.parsed.status !== 'success' && result.parsed.status !== 'error')) {\n return {\n endpoint: input.endpoint,\n status: 'fail',\n httpStatus: result.httpStatus,\n reason: `fuzz payload produced non-envelope response: ${JSON.stringify(payload).slice(0, 80)}`,\n durationMs,\n };\n }\n\n //? The limiter answers before input validation, so this payload (and every\n //? later one in the same window) never reached the route. Report what was\n //? actually exercised instead of counting the rejections as passes.\n if (isRateLimitedCode(result.parsed.errorCode)) {\n return {\n endpoint: input.endpoint,\n status: 'skipped',\n httpStatus: result.httpStatus,\n errorCode: result.parsed.errorCode,\n reason: `rate-limited after ${String(index)} of ${String(JUNK_PAYLOADS.length)} fuzz payloads (not exercised); pass a reset token so the sweep resets rate limits between routes`,\n durationMs,\n };\n }\n\n if (result.parsed.status === 'error' && typeof result.parsed.errorCode !== 'string') {\n return {\n endpoint: input.endpoint,\n status: 'fail',\n httpStatus: result.httpStatus,\n reason: `fuzz error response missing errorCode for payload: ${JSON.stringify(payload).slice(0, 80)}`,\n durationMs,\n };\n }\n }\n\n return {\n endpoint: input.endpoint,\n status: 'pass',\n durationMs: Date.now() - started,\n };\n};\n","import { runFuzzCheck } from './fuzzCheck';\nimport { resetServerState } from './resetServerState';\nimport {\n shouldSkip,\n calculateSummary,\n hasSessionHeaders,\n selectEndpoints,\n transportFor,\n STATE_CHANGING_METHODS,\n} from './testLayerHelpers';\nimport type { ProbeTransport } from './probeRequest';\nimport type { ApiMethodMap, ContractCheckResult, RunContractSummary, SyncMetaMap } from './types';\n\nexport interface RunFuzzTestsInput {\n apiMethodMap: ApiMethodMap;\n /** Generated `syncMetaMap`. When given, sync routes are swept too. */\n syncMetaMap?: SyncMetaMap;\n baseUrl: string;\n skip?: string[];\n /** Substring filter on `<page>/<name>/<version>`; other routes are not requested at all. */\n filter?: string;\n headers?: Record<string, string>;\n /** Transport for sync routes (e.g. the socket transport). Defaults to HTTP. */\n syncTransport?: ProbeTransport;\n /** Transport for API routes (e.g. the socket transport). Defaults to HTTP. */\n apiTransport?: ProbeTransport;\n /**\n * When given, rate limits are reset (`/_test/reset?only=rateLimits`) before\n * each route, so the shared per-IP bucket cannot fill up mid-sweep and turn\n * the remaining payloads into limiter rejections.\n */\n resetToken?: string;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runFuzzTests = async (input: RunFuzzTestsInput): Promise<RunContractSummary> => {\n const endpoints = selectEndpoints(input);\n const skip = input.skip ?? [];\n //? In an authenticated sweep (session Cookie or Bearer token) junk bodies sent\n //? to state-changing routes (POST/PUT/DELETE — every sync route) bypass the\n //? body-ignore short-circuit and reach real mutation handlers — risking\n //? unintended side-effects on the test database. Skip those endpoints here;\n //? the contract layer already validates their shape with well-formed input,\n //? and the CSRF layer probes their auth guard separately.\n const isAuthenticatedSweep = hasSessionHeaders(input.headers);\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n if (isAuthenticatedSweep && STATE_CHANGING_METHODS.has(endpoint.method)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'state-changing route skipped in authenticated fuzz sweep (mutation risk with junk bodies)',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (input.resetToken) {\n await resetServerState({ baseUrl: input.baseUrl, token: input.resetToken, only: ['rateLimits'] });\n }\n\n const result = await runFuzzCheck({\n endpoint,\n baseUrl: input.baseUrl,\n headers: input.headers,\n transport: transportFor(endpoint, input.syncTransport, input.apiTransport),\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","//? Socket.io probe transport. A project on the default\n//? `transport.invocation: 'socket'` sends every API and sync request over the\n//? socket, which runs different server pipelines (`handleApiRequest`,\n//? `handleSyncRequest`) than the `/api/*` and `/sync/*` HTTP routes. Probing over\n//? HTTP alone would leave those pipelines — the ones real users hit — without\n//? coverage (ADR 0064).\n//?\n//? One socket per session identity (the probe's session Cookie or Bearer\n//? header, or none for an anonymous probe), opened lazily and reused for the\n//? whole sweep. An authenticated socket joins `receiver` once, so\n//? room-membership enforcement (`sync.requireRoomMembership`) sees a member,\n//? exactly like a real client that joined its room before syncing.\n\nimport type { Socket } from 'socket.io-client';\nimport { tryCatch } from '@luckystack/core';\n\nimport { connectTestSocket, emitApiOverSocket, emitSyncOverSocket, joinTestRoom, releaseTestSocket } from './testSocket';\nimport type { ProbeResponse, ProbeTransport, ProbeTransportRequest } from './probeRequest';\n\nconst LABEL = 'socket probe';\n\nexport interface SocketProbeTransportOptions {\n baseUrl: string;\n /** Room every sync probe targets; authenticated sockets join it first. */\n receiver: string;\n /** Session cookie name, used to read the token out of a probe's Cookie header. */\n sessionCookieName: string;\n}\n\n//? Pull `<token>` out of a `Cookie` header that carries `<cookieName>=<token>`.\nconst tokenFromCookieHeader = (cookieHeader: string | undefined, cookieName: string): string | null => {\n if (!cookieHeader) return null;\n for (const part of cookieHeader.split(';')) {\n const [rawName, ...rest] = part.trim().split('=');\n if (rawName === cookieName && rest.length > 0) return rest.join('=');\n }\n return null;\n};\n\n//? Token-mode sweeps carry the session as `Authorization: Bearer <token>`.\nconst tokenFromHeaders = (headers: Record<string, string> | undefined, cookieName: string): string | null => {\n const cookieToken = tokenFromCookieHeader(headers?.Cookie, cookieName);\n if (cookieToken) return cookieToken;\n const authorization = headers?.Authorization;\n return authorization?.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : null;\n};\n\n//? Socket replies carry the status the HTTP route would have answered with in\n//? the envelope's `httpStatus`, so a 5xx is visible to the checks either way.\nconst toProbeResponse = (payload: unknown): ProbeResponse => {\n if (typeof payload !== 'object' || payload === null) return { parsed: null };\n const record = payload as { status?: unknown; errorCode?: unknown; httpStatus?: unknown };\n return {\n ...(typeof record.httpStatus === 'number' ? { httpStatus: record.httpStatus } : {}),\n parsed: {\n status: typeof record.status === 'string' ? record.status : undefined,\n errorCode: typeof record.errorCode === 'string' ? record.errorCode : undefined,\n },\n };\n};\n\nexport const createSocketProbeTransport = (options: SocketProbeTransportOptions): ProbeTransport => {\n //? Keyed by token ('' = anonymous). A failed connect is cached as `null` so\n //? an unreachable socket endpoint fails each probe fast instead of retrying.\n const sockets = new Map<string, Promise<Socket | null>>();\n\n const socketFor = (token: string | null): Promise<Socket | null> => {\n const key = token ?? '';\n const existing = sockets.get(key);\n if (existing) return existing;\n const opening = (async (): Promise<Socket | null> => {\n const [connectError, socket] = await tryCatch(() => connectTestSocket({\n baseUrl: options.baseUrl,\n token,\n label: LABEL,\n sessionCookieName: options.sessionCookieName,\n }));\n if (connectError || !socket) return null;\n //? Anonymous sockets cannot join (the server answers `auth.required`);\n //? their probes then exercise exactly what an anonymous client gets.\n if (token) await tryCatch(() => joinTestRoom(socket, options.receiver, { label: LABEL }));\n return socket;\n })();\n sockets.set(key, opening);\n return opening;\n };\n\n const send = async (request: ProbeTransportRequest): Promise<ProbeResponse | null> => {\n const socket = await socketFor(tokenFromHeaders(request.headers, options.sessionCookieName));\n if (!socket) return null;\n const routePath = `${request.endpoint.page}/${request.endpoint.name}/${request.endpoint.version}`;\n const outcome = request.endpoint.kind === 'sync'\n ? await emitSyncOverSocket(socket, {\n routePath,\n data: request.body,\n receiver: options.receiver,\n timeoutMs: request.requestTimeoutMs,\n })\n : await emitApiOverSocket(socket, { routePath, data: request.body, timeoutMs: request.requestTimeoutMs });\n return outcome.kind === 'response' ? toProbeResponse(outcome.payload) : null;\n };\n\n const close = async (): Promise<void> => {\n const opened = [...sockets.entries()];\n sockets.clear();\n for (const [key, pending] of opened) {\n const socket = await pending;\n if (!socket) continue;\n //? An authenticated socket runs on the caller's session — release it so\n //? presence does not log that session out when the socket closes.\n if (key) await releaseTestSocket(socket);\n else socket.disconnect();\n }\n };\n\n return { name: 'socket', send, close };\n};\n","//? Socket.io client plumbing shared by every test-runner surface that speaks the\n//? socket transport: `ctx.watchStream` (observer socket), `ctx.callSync` in\n//? socket mode, and the sweep layers' socket probe transport. One place owns\n//? the handshake auth, the connect wait, the `joinRoom` ack and the `sync`\n//? request/response correlation, so the wire contract cannot drift between them.\n//?\n//? Wire contract (sourced from `@luckystack/sync` `syncRequest.ts`, the real\n//? client): emit `sync` with `{ name, data, cb, receiver, responseIndex,\n//? ignoreSelf }`; the server answers once on `sync-<responseIndex>`. API calls\n//? (sourced from `@luckystack/core` `apiRequest.ts`): emit `apiRequest` with\n//? `{ name, data, responseIndex }`; the server answers once on\n//? `apiResponse-<responseIndex>`.\n\nimport { io as createSocketIoClient, Socket } from 'socket.io-client';\nimport {\n applyCookiePrefixConstraints,\n buildApiResponseEventName,\n buildJoinRoomResponseEventName,\n buildSyncResponseEventName,\n getProjectConfig,\n socketEventNames,\n tryCatch,\n} from '@luckystack/core';\n\nconst DEFAULT_CONNECT_TIMEOUT_MS = 3000;\nconst DEFAULT_JOIN_TIMEOUT_MS = 3000;\n\n//? The session cookie name the server sets and reads includes\n//? `http.sessionCookiePrefix` (`__Host-` / `__Secure-`). A header built from\n//? the bare `sessionCookieName` made every authenticated probe anonymous on a\n//? project that configures a prefix.\nexport const projectSessionCookieName = (): string => {\n const { http } = getProjectConfig();\n return applyCookiePrefixConstraints(http.sessionCookieName, http.sessionCookiePrefix).name;\n};\n\n//? Monotonic per-process counter. Every request that needs a correlated reply\n//? (`joinRoom-<n>`, `sync-<n>`) takes a fresh index so concurrent sockets and\n//? sibling watchers in the same process never share a response event name.\nlet responseIndexCounter = 0;\nexport const nextResponseIndex = (): number => {\n responseIndexCounter += 1;\n return responseIndexCounter;\n};\n\nexport interface ConnectTestSocketInput {\n baseUrl: string;\n /** Session token, or null for an anonymous socket. */\n token: string | null;\n /** Prefix for error messages (e.g. `watchStream`, `callSync`). */\n label: string;\n /** Session cookie name. Defaults to the project's configured cookie. */\n sessionCookieName?: string;\n connectTimeoutMs?: number;\n}\n\nconst waitForConnect = (socket: Socket, label: string, timeoutMs: number): Promise<void> => {\n return new Promise<void>((resolve, reject) => {\n if (socket.connected) {\n resolve();\n return;\n }\n const timer = setTimeout(() => {\n cleanup();\n reject(new Error(`${label}: socket failed to connect within ${timeoutMs}ms`));\n }, timeoutMs);\n const onConnect = (): void => {\n cleanup();\n resolve();\n };\n const onConnectError = (err: Error): void => {\n cleanup();\n reject(new Error(`${label}: connect_error — ${err.message}`));\n };\n const cleanup = (): void => {\n clearTimeout(timer);\n socket.off('connect', onConnect);\n socket.off('connect_error', onConnectError);\n };\n socket.on('connect', onConnect);\n socket.on('connect_error', onConnectError);\n });\n};\n\n/**\n * Open a socket.io connection authenticated the way a browser would be, and\n * wait until it is connected. On a connect timeout / connect_error the socket\n * is torn down before the error is thrown, so a failed attempt never leaks.\n */\nexport const connectTestSocket = async (input: ConnectTestSocketInput): Promise<Socket> => {\n //? Authenticate in BOTH token modes. `auth.token` is the sessionBasedToken\n //? path; in the DEFAULT cookie mode the server reads the token ONLY from the\n //? session cookie (extractTokenFromSocket ignores handshake.auth.token unless\n //? `acceptBearerInCookieMode`), so without the Cookie header the socket would\n //? connect anonymously. In Node, socket.io-client honours `extraHeaders` on the\n //? websocket handshake, so the cookie is set there.\n const sessionCookieName = input.sessionCookieName ?? projectSessionCookieName();\n const socket: Socket = createSocketIoClient(input.baseUrl, {\n transports: ['websocket'],\n forceNew: true,\n reconnection: false,\n auth: input.token ? { token: input.token } : undefined,\n extraHeaders: input.token ? { Cookie: `${sessionCookieName}=${input.token}` } : undefined,\n });\n\n const [connectError] = await tryCatch(\n () => waitForConnect(socket, input.label, input.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS),\n );\n if (connectError) {\n socket.disconnect();\n throw connectError;\n }\n return socket;\n};\n\nconst RELEASE_TIMEOUT_MS = 1000;\n\n/**\n * Close a socket that runs on a session the runner does NOT own (the sweep's\n * `authToken`) without ending that session.\n *\n * With `@luckystack/presence` installed, a token whose last socket closes is\n * logged out once the short disconnect grace window expires — a closed browser\n * tab ends the session. A plain `disconnect()` therefore deleted the caller's\n * session seconds after every sweep. Sending `intentionalDisconnect` first is\n * the signal a real client sends on a tab switch: presence keeps the session\n * and the server closes the socket itself. Without presence nobody listens,\n * so the socket is closed locally after a short wait.\n */\nexport const releaseTestSocket = async (socket: Socket): Promise<void> => {\n if (!socket.connected) return;\n await new Promise<void>((resolve) => {\n const timer = setTimeout(() => {\n socket.off('disconnect', onDisconnect);\n resolve();\n }, RELEASE_TIMEOUT_MS);\n const onDisconnect = (): void => {\n clearTimeout(timer);\n resolve();\n };\n socket.once('disconnect', onDisconnect);\n socket.emit(socketEventNames.intentionalDisconnect);\n });\n socket.disconnect();\n};\n\n/**\n * Join `room` and wait for the server's ack. Rejects when the server refuses\n * the join (e.g. `auth.required` for an anonymous socket) or never answers.\n */\nexport const joinTestRoom = (\n socket: Socket,\n room: string,\n options: { label: string; timeoutMs?: number },\n): Promise<void> => {\n const timeoutMs = options.timeoutMs ?? DEFAULT_JOIN_TIMEOUT_MS;\n const responseIndex = nextResponseIndex();\n const eventName = buildJoinRoomResponseEventName(responseIndex);\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n socket.off(eventName, onAck);\n reject(new Error(`${options.label}: joinRoom ack timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n const onAck = (payload: unknown): void => {\n clearTimeout(timer);\n socket.off(eventName, onAck);\n if (typeof payload === 'object' && payload !== null && (payload as { status?: unknown }).status === 'error') {\n const errorCode = (payload as { errorCode?: unknown }).errorCode;\n reject(new Error(`${options.label}: joinRoom rejected — ${typeof errorCode === 'string' ? errorCode : 'unknown'}`));\n return;\n }\n resolve();\n };\n socket.on(eventName, onAck);\n socket.emit(socketEventNames.joinRoom, { group: room, responseIndex });\n });\n};\n\nexport interface EmitSyncInput {\n /** `<page>/<name>/<version>` — the route path without the `sync/` prefix. */\n routePath: string;\n data: unknown;\n receiver: string | undefined;\n ignoreSelf?: boolean;\n timeoutMs: number;\n}\n\nexport type EmitSyncOutcome =\n | { kind: 'response'; payload: unknown }\n | { kind: 'timeout' };\n\n//? Emit one request and resolve with the single reply on `replyEvent`, or a\n//? timeout. The listener is attached before the emit so a fast reply is not missed.\nconst emitAndAwaitReply = (\n socket: Socket,\n replyEvent: string,\n emit: () => void,\n timeoutMs: number,\n): Promise<EmitSyncOutcome> => new Promise<EmitSyncOutcome>((resolve) => {\n const timer = setTimeout(() => {\n socket.off(replyEvent, onResponse);\n resolve({ kind: 'timeout' });\n }, timeoutMs);\n const onResponse = (payload: unknown): void => {\n clearTimeout(timer);\n socket.off(replyEvent, onResponse);\n resolve({ kind: 'response', payload });\n };\n socket.on(replyEvent, onResponse);\n emit();\n});\n\n/**\n * Send one sync request over `socket` exactly like the real client does and\n * resolve with the server's single reply on `sync-<responseIndex>`.\n */\nexport const emitSyncOverSocket = (socket: Socket, input: EmitSyncInput): Promise<EmitSyncOutcome> => {\n const responseIndex = nextResponseIndex();\n return emitAndAwaitReply(socket, buildSyncResponseEventName(responseIndex), () => {\n socket.emit(socketEventNames.sync, {\n name: `sync/${input.routePath}`,\n //? Only `undefined` defaults — a fuzz probe's `null` must reach the server as-is.\n data: input.data === undefined ? {} : input.data,\n //? Same fallback cancel key the real client sends before the server's\n //? `__cancelId` handshake arrives; the runner never cancels, so it is\n //? only there to keep the request shape identical.\n cb: input.routePath,\n receiver: input.receiver,\n responseIndex,\n ignoreSelf: input.ignoreSelf ?? false,\n });\n }, input.timeoutMs);\n};\n\nexport interface EmitApiInput {\n /** `<page>/<name>/<version>` — the route path without the `api/` prefix. */\n routePath: string;\n data: unknown;\n timeoutMs: number;\n}\n\n/**\n * Send one API request over `socket` exactly like the real client does in\n * socket mode and resolve with the server's single reply on\n * `apiResponse-<responseIndex>`.\n */\nexport const emitApiOverSocket = (socket: Socket, input: EmitApiInput): Promise<EmitSyncOutcome> => {\n const responseIndex = nextResponseIndex();\n return emitAndAwaitReply(socket, buildApiResponseEventName(responseIndex), () => {\n socket.emit(socketEventNames.apiRequest, {\n name: `api/${input.routePath}`,\n //? Only `undefined` defaults — a fuzz probe's `null` must reach the server as-is.\n data: input.data === undefined ? {} : input.data,\n responseIndex,\n });\n }, input.timeoutMs);\n};\n","import type { z } from 'zod';\n\n//? Options threaded through the whole walk so nested string fields get the\n//? same treatment as top-level ones.\nexport interface SampleSchemaOptions {\n /**\n * Marker prefix applied to generated UNCONSTRAINED string values so any rows\n * a sweep creates are identifiable + collision-free (finding #98). Strings\n * that carry a format (email/url/uuid/datetime/…) or a check (min/max/\n * length/regex) are left as-is, since prefixing could break the route's own\n * validation and defeat the contract layer's \"valid input\" guarantee.\n */\n stringPrefix?: string;\n}\n\n//? Walk a Zod schema and produce a minimal valid value. Used by the\n//? contract/fuzz runners so \"valid input\" tests don't require hand-written\n//? fixtures per endpoint. Handles the shapes the generator emits\n//? (primitives, literals, unions, optionals, objects, records, arrays).\n//?\n//? Not a property-based generator — returns the SAME deterministic value\n//? every call. For randomized fuzz, swap to `fast-check` in a later session.\n\n//? zod 4 renamed the internal def accessor from `._def` to `.def`; recurse\n//? through whichever a child node exposes so the walker handles both versions.\nconst innerDef = (node: { _def?: unknown; def?: unknown } | undefined): unknown =>\n node ? (node.def ?? node._def) : undefined;\n\nconst sampleForDef = (def: unknown, options: SampleSchemaOptions | undefined): unknown => {\n //? Zod stores its runtime shape on `_def.type` (v3) or `_def.typeName`.\n //? Access is untyped because `def` can be any of dozens of internal shapes.\n const d = def as {\n typeName?: string;\n type?: string;\n innerType?: { _def?: unknown; def?: unknown };\n values?: unknown[] | Record<string, unknown>;\n entries?: Record<string, unknown>;\n value?: unknown;\n options?: { _def?: unknown; def?: unknown }[];\n shape?: (() => Record<string, { _def?: unknown; def?: unknown }>) | Record<string, { _def?: unknown; def?: unknown }>;\n valueType?: { _def: unknown };\n element?: { _def: unknown };\n items?: { _def?: unknown; def?: unknown }[];\n schema?: { _def?: unknown; def?: unknown };\n in?: { _def?: unknown; def?: unknown };\n //? String-constraint markers. zod 4 sets `format` + `check: 'string_format'`\n //? for email/url/uuid/datetime/cuid/… and a `checks` array for min/max/\n //? length/regex; zod 3 puts the lot on `checks`. A bare string has none.\n format?: string;\n check?: string;\n checks?: unknown[];\n };\n\n const name = d.typeName ?? d.type;\n\n switch (name) {\n case 'ZodString':\n case 'string': {\n //? Prefix only UNCONSTRAINED strings with the caller's test-data marker\n //? (finding #98). A format (email/url/uuid/datetime/…) or any check\n //? (min/max/length/regex) means a prefixed/lengthened value could fail the\n //? route's own Zod, turning the happy-path probe into a validation-error\n //? test — so those keep the plain `'test'`.\n const isConstrained = Boolean(d.format)\n || d.check === 'string_format'\n || (Array.isArray(d.checks) && d.checks.length > 0);\n if (options?.stringPrefix && !isConstrained) return `${options.stringPrefix}test`;\n return 'test';\n }\n case 'ZodNumber':\n case 'number': {\n return 0;\n }\n case 'ZodBoolean':\n case 'boolean': {\n return false;\n }\n case 'ZodNull':\n case 'null': {\n return null;\n }\n case 'ZodUndefined':\n case 'undefined': {\n return undefined;\n }\n case 'ZodAny':\n case 'any':\n case 'ZodUnknown':\n case 'unknown': {\n return null;\n }\n case 'ZodLiteral':\n case 'literal': {\n //? zod 3 stores the literal on `_def.value`; zod 4 stores `_def.values` (array).\n return d.value ?? (Array.isArray(d.values) ? d.values[0] : undefined);\n }\n case 'ZodOptional':\n case 'optional':\n case 'ZodNullable':\n case 'nullable': {\n //? Omitting optional fields is the most conservative valid input.\n return undefined;\n }\n case 'ZodEnum':\n case 'enum':\n case 'ZodNativeEnum':\n case 'nativeEnum': {\n //? Return the first declared value so an enum-typed required field gets a\n //? VALID member rather than `null` (which the route's own Zod rejects,\n //? silently turning the contract probe into a validation-error test).\n //? zod 3 stores members on `_def.values` (array); zod 4 on `_def.entries`\n //? (object) or `_def.values` (Set/array).\n if (Array.isArray(d.values)) return d.values[0];\n const entries = d.entries ?? (d.values && !Array.isArray(d.values) ? d.values : undefined);\n if (entries) {\n const first = Object.values(entries)[0];\n if (first !== undefined) return first;\n }\n return undefined;\n }\n case 'ZodDefault':\n case 'default':\n case 'ZodCatch':\n case 'catch': {\n //? Unwrap to the inner schema's sample (the field is still required at the\n //? type level; the default only applies when omitted).\n return sampleForDef(innerDef(d.innerType), options);\n }\n case 'ZodEffects':\n case 'effects':\n case 'ZodPipeline':\n case 'pipe':\n case 'ZodBranded':\n case 'branded':\n case 'ZodReadonly':\n case 'readonly': {\n //? `.refine`/`.transform`/`.pipe`/`.brand`/`.readonly` wrap an inner\n //? schema — recurse into it. A `.refine` may still reject the sample, but\n //? recursing yields a structurally-valid base far more often than `null`.\n return sampleForDef(innerDef(d.schema ?? d.in ?? d.innerType), options);\n }\n case 'ZodTuple':\n case 'tuple': {\n const items = d.items ?? [];\n return items.map(item => sampleForDef(innerDef(item), options));\n }\n case 'ZodUnion':\n case 'union':\n case 'ZodDiscriminatedUnion':\n case 'discriminatedUnion': {\n return d.options && d.options.length > 0 && d.options[0] ? sampleForDef(innerDef(d.options[0]), options) : undefined;\n }\n case 'ZodArray':\n case 'array': {\n return [];\n }\n case 'ZodObject':\n case 'object': {\n const out: Record<string, unknown> = {};\n //? zod 3 exposes `_def.shape` as a function; zod 4 as a plain object.\n const shape = typeof d.shape === 'function' ? d.shape() : (d.shape ?? {});\n for (const [key, valueSchema] of Object.entries(shape)) {\n const sample = sampleForDef(innerDef(valueSchema), options);\n if (sample !== undefined) out[key] = sample;\n }\n return out;\n }\n case 'ZodRecord':\n case 'record': {\n return {};\n }\n case 'ZodDate':\n case 'date': {\n return new Date().toISOString();\n }\n default: {\n //? Unknown node: OMIT (undefined) rather than emit `null`. A `null` for a\n //? required field of an unhandled type makes the route's own Zod reject\n //? the body, silently turning the \"happy path\" contract probe into a\n //? validation-error test. Omitting lets the object walker drop the key so\n //? the probe only fails if the field was genuinely required (a real,\n //? visible signal) instead of always.\n return undefined;\n }\n }\n};\n\nexport const sampleSchemaInput = (schema: z.ZodType, options?: SampleSchemaOptions): unknown => {\n //? zod 4 renamed the public def accessor from `._def` to `.def`; fall back to\n //? `._def` (zod 3) so the top-level entry handles both, matching the\n //? recursive `innerDef` helper.\n const node = schema as { def?: unknown; _def?: unknown };\n return sampleForDef(node.def ?? node._def, options);\n};\n","//? Builds the per-endpoint sample payload the sweep layers and registered\n//? custom layers send: a Zod-valid value generated from the route's runtime\n//? input schema (`apiInputSchemas` for API routes, `syncInputSchemas` for sync\n//? routes — both emitted by devkit), or `{}` when the route has no schema.\n\nimport type { ZodType } from 'zod';\n\nimport { sampleSchemaInput } from './schemaSampleInput';\nimport type { EndpointDescriptor } from './types';\n\nexport type InputSchemaMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, ZodType | undefined>>>>>>;\n\nexport interface CreateSampleInputForOptions {\n apiInputSchemas?: InputSchemaMap;\n syncInputSchemas?: InputSchemaMap;\n /**\n * Prefix for generated unconstrained strings — pass the run's\n * `createTestDataMarker()` so rows the probes create are identifiable.\n */\n stringPrefix?: string;\n}\n\nexport const createSampleInputFor = (options: CreateSampleInputForOptions) =>\n (endpoint: EndpointDescriptor): unknown => {\n const schemas = endpoint.kind === 'sync' ? options.syncInputSchemas : options.apiInputSchemas;\n const schema = schemas?.[endpoint.page]?.[endpoint.name]?.[endpoint.version];\n return schema ? sampleSchemaInput(schema, { stringPrefix: options.stringPrefix }) : {};\n };\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nconst isProcessRunning = (pid: unknown): boolean => {\n if (typeof pid !== 'number' || !Number.isInteger(pid) || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return error instanceof Error && 'code' in error && error.code === 'EPERM';\n }\n};\n\nexport interface ResolveTestBaseUrlOptions {\n /** Project root containing node_modules/.luckystack/dev-server.json. */\n cwd?: string;\n /** Used when no explicit override or valid live advertisement exists. */\n fallbackUrl?: string;\n}\n\n//? Shared by the framework repo and generated consumers so a dev backend that\n//? auto-increments off its configured port does not turn connection failures\n//? against the stale port into false test failures.\nexport const resolveTestBaseUrl = (\n options: ResolveTestBaseUrlOptions = {},\n): string => {\n const explicit = process.env.TEST_BASE_URL;\n if (explicit) return explicit;\n\n try {\n const raw = fs.readFileSync(\n path.join(options.cwd ?? process.cwd(), 'node_modules', '.luckystack', 'dev-server.json'),\n 'utf8',\n );\n const info = JSON.parse(raw) as { port?: unknown; pid?: unknown };\n if (\n typeof info.port === 'number'\n && Number.isInteger(info.port)\n && info.port > 0\n && info.port <= 65_535\n && isProcessRunning(info.pid)\n ) {\n return `http://localhost:${String(info.port)}`;\n }\n } catch {\n //? No live dev advertisement — use the caller's config-derived fallback.\n }\n\n return options.fallbackUrl ?? 'http://localhost:80';\n};\n","//? @adr 0032 — test-process env/secret bootstrap through lazy consumer config.\n//? Test-process bootstrap for environment-backed integrations. The live server\n//? resolves secret-manager pointers in its own process, but Layer-5 tests also\n//? use Prisma/Redis directly inside the runner process. That process therefore\n//? needs the same env prefix BEFORE custom test modules are imported.\n\nimport { loadEnvFiles, tryCatch } from '@luckystack/core';\n\ntype MaybePromise<T> = T | Promise<T>;\n\ninterface SecretManagerConfigLike {\n url: string;\n token: string | { fromFile: string };\n [key: string]: unknown;\n}\n\nexport interface ResolveTestEnvironmentInput {\n /**\n * Loads the consumer's default-exported `config.ts` object after `.env` files\n * are available. Keeping this as a callback avoids hardcoding a consumer path\n * inside the package and keeps `@luckystack/secret-manager` optional.\n */\n loadProjectConfig?: () => MaybePromise<unknown>;\n}\n\ninterface RequiredProjectConfigLoader {\n loadProjectConfig: NonNullable<ResolveTestEnvironmentInput['loadProjectConfig']>;\n}\n\n/** @internal Runtime guard for untyped callers of the public orchestrators. */\nexport const assertProjectConfigLoader: (\n input: unknown,\n caller: 'runAllTests' | 'runCustomTests',\n) => asserts input is RequiredProjectConfigLoader = (input, caller) => {\n if (\n typeof input !== 'object'\n || input === null\n || !('loadProjectConfig' in input)\n || typeof input.loadProjectConfig !== 'function'\n ) {\n throw new TypeError(`[test-runner] ${caller} requires a lazy loadProjectConfig callback`);\n }\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null;\n\nconst isToken = (value: unknown): value is SecretManagerConfigLike['token'] =>\n typeof value === 'string'\n || (isRecord(value) && typeof value.fromFile === 'string');\n\nconst readSecretManagerConfig = (projectConfig: unknown): SecretManagerConfigLike | null => {\n if (!isRecord(projectConfig)) return null;\n const candidate = projectConfig.secretManager;\n if (!isRecord(candidate)) return null;\n\n const url = candidate.url;\n if (url === undefined || url === '') return null;\n if (typeof url !== 'string') {\n throw new TypeError('[test-runner] config.secretManager.url must be a string.');\n }\n if (!isToken(candidate.token)) {\n throw new TypeError(\n '[test-runner] config.secretManager.token must be a string or { fromFile: string }.',\n );\n }\n\n return { ...candidate, url, token: candidate.token };\n};\n\n/**\n * Load the project's env files and resolve optional secret-manager pointers for\n * the TEST process. Call before importing integration-test modules that touch a\n * database, Redis, or another env-backed SDK.\n */\nexport const resolveTestEnvironment = async (\n input: ResolveTestEnvironmentInput = {},\n): Promise<void> => {\n loadEnvFiles();\n if (!input.loadProjectConfig) return;\n\n const projectConfig = await input.loadProjectConfig();\n const secretManagerConfig = readSecretManagerConfig(projectConfig);\n if (!secretManagerConfig) return;\n\n const [importError, secretManager] = await tryCatch(\n () => import('@luckystack/secret-manager'),\n );\n if (importError || !secretManager) {\n throw new Error(\n '[test-runner] config.secretManager.url is set, but @luckystack/secret-manager '\n + 'could not be loaded. Install the package or remove the secret-manager config; '\n + 'tests will not continue with unresolved env pointers.',\n { cause: importError ?? undefined },\n );\n }\n\n await secretManager.initSecretManager({\n ...secretManagerConfig,\n //? Match the normal server/ORM bootstrap: configured remote resolution is\n //? fail-fast. A raw DATABASE_URL_V<n> must never leak into Prisma.\n source: 'remote',\n });\n};\n","import { randomUUID } from 'node:crypto';\n\n//? Contract-test mutation safety (finding #98). The contract sweep posts\n//? generated sample payloads at every endpoint, which can create real rows.\n//? Tagging generated string values with a recognizable, run-unique marker\n//? makes any such row identifiable (so the reset bookends — and a human\n//? scanning the DB — can tell test data from real data) and collision-free\n//? (no chance a generated value matches an existing real record's field).\n\n//? Fixed leading token every generated test string starts with. Stable across\n//? runs so consumers / DB scans can match `lstest_*` to spot stray test data.\nexport const TEST_DATA_PREFIX = 'lstest_';\n\n//? Build a run-unique marker like `lstest_a1b2c3d4_`. The short uuid segment\n//? keeps two concurrent runs from generating colliding values while staying\n//? short enough not to blow past typical string length limits.\nexport const createTestDataMarker = (): string => `${TEST_DATA_PREFIX}${randomUUID().slice(0, 8)}_`;\n","import type { ContractCheckResult, RunContractSummary } from './types';\n\nconst statusIcon = (status: ContractCheckResult['status']): string => {\n switch (status) {\n case 'pass': { return '[PASS]';\n }\n case 'fail': { return '[FAIL]';\n }\n case 'skipped': { return '[SKIP]';\n }\n }\n};\n\nexport const logContractResult = (result: ContractCheckResult): void => {\n const { endpoint, durationMs, httpStatus, responseStatus, errorCode, reason } = result;\n const icon = statusIcon(result.status);\n const core = `${icon} ${endpoint.method} /${endpoint.fullPath} ${durationMs}ms`;\n\n if (result.status === 'pass') {\n const tail = responseStatus === 'error' ? ` (error: ${errorCode ?? 'unknown'})` : '';\n console.log(`${core} http=${httpStatus ?? '-'}${tail}`);\n return;\n }\n\n if (result.status === 'skipped') {\n console.log(`${core} ${reason ?? ''}`);\n return;\n }\n\n console.log(`${core} http=${httpStatus ?? '-'} reason=${reason ?? '(none)'}`);\n};\n\nexport const logContractSummary = (summary: RunContractSummary): void => {\n const { total, passed, failed, skipped } = summary;\n console.log('');\n console.log(`Contract tests: ${passed}/${total} passed, ${failed} failed, ${skipped} skipped`);\n\n if (failed > 0) {\n console.log('');\n console.log('Failures:');\n for (const result of summary.results) {\n if (result.status !== 'fail') continue;\n console.log(` - /${result.endpoint.fullPath}: ${result.reason ?? '(no reason)'}`);\n }\n }\n};\n","//? Per-route business-logic test layer. Discovers `*.tests.ts` files\n//? alongside `_api/` and `_sync/` route source, dynamic-imports each,\n//? builds a route-bound `TestContext`, runs the exported `customTests`\n//? cases. Spec: docs/ARCHITECTURE_TESTING.md.\n\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport {\n buildRoutedGetUrl,\n getCsrfConfig,\n getPrismaClient,\n getProjectConfig,\n getSrcDir,\n tryCatch,\n tryCatchSync,\n} from '@luckystack/core';\n\nimport {\n assertProjectConfigLoader,\n resolveTestEnvironment,\n type ResolveTestEnvironmentInput,\n} from './resolveTestEnvironment';\nimport type { Socket } from 'socket.io-client';\n\nimport {\n openStreamWatcher,\n type StreamChunkFrame,\n type StreamWatcher,\n} from './streamWatcher';\nimport { connectTestSocket, emitApiOverSocket, emitSyncOverSocket, joinTestRoom, projectSessionCookieName } from './testSocket';\n//? `ApiMethodMap` mirrors the generated `apiMethodMap` shape (page → name →\n//? version → method) and is a type-only import, so there's still no import-time\n//? coupling to the consumer's generated artifact.\nimport type { ApiMethodMap } from './types';\n\nconst API_TEST_FILE_PATTERN = /_v(\\d+)\\.tests\\.ts$/;\nconst SYNC_SERVER_TEST_FILE_PATTERN = /_server_v(\\d+)\\.tests\\.ts$/;\n\n//? Default per-request timeout for the Layer-5 call helpers. Mirrors the sweep\n//? layers so a hung (non-erroring) server can't wedge the whole custom-test run\n//? indefinitely — without it the case's own tryCatch never fires.\nconst DEFAULT_CALL_TIMEOUT_MS = 10_000;\n\nexport interface CustomTestCase {\n name: string;\n run: (ctx: TestContext) => Promise<void>;\n /**\n * Mark this case as a KNOWN, accepted failure (e.g. a documented bug not\n * yet fixed, or a scenario the current implementation cannot satisfy). The\n * string is the reason shown in the report. A case marked `expectedToFail`\n * that throws is reported as `xfail` — counted separately, NOT a red\n * failure. If it unexpectedly PASSES it is reported as `xpass` so the stale\n * marker can be removed. Leave undefined for normal cases.\n */\n expectedToFail?: string;\n}\n\nexport interface CustomTestResult {\n routePath: string;\n caseName: string;\n //? `xfail` = marked expectedToFail and failed as expected (known issue).\n //? `xpass` = marked expectedToFail but passed (remove the marker).\n status: 'pass' | 'fail' | 'xfail' | 'xpass';\n durationMs: number;\n reason?: string;\n /** Documented reason when the case carried an `expectedToFail` marker. */\n expectedToFail?: string;\n}\n\nexport interface RunCustomTestsSummary {\n total: number;\n passed: number;\n failed: number;\n /** Cases marked `expectedToFail` that failed as expected (known issues). */\n xfailed: number;\n /** Cases marked `expectedToFail` that unexpectedly passed (remove the marker). */\n xpassed: number;\n results: CustomTestResult[];\n}\n\nexport interface RunCustomTestsInput {\n baseUrl: string;\n /** Optional cookie name for the session token. Defaults to the project's configured cookie. */\n sessionCookieName?: string;\n /** Filter to a subset of routes — substring match against `<page>/<name>/<version>`. */\n filter?: string;\n /**\n * Generated `apiMethodMap` so the harness sends each API route its declared\n * HTTP method (e.g. logout is DELETE). Falls back to POST when absent.\n */\n apiMethodMap?: ApiMethodMap;\n /**\n * Lazy consumer config for direct Layer-5 runs. Required so the public path\n * cannot silently skip a configured secret manager; `runAllTests` prepares\n * the environment separately before using the internal Layer-5 entrypoint.\n */\n loadProjectConfig: NonNullable<ResolveTestEnvironmentInput['loadProjectConfig']>;\n onResult?: (result: CustomTestResult) => void;\n}\n\ntype PreparedRunCustomTestsInput = Omit<RunCustomTestsInput, 'loadProjectConfig'>;\n\ninterface DiscoveredTestFile {\n /** Absolute path to the test file. */\n filePath: string;\n /** `api` or `sync` — which transport this route uses. */\n kind: 'api' | 'sync';\n /** Route descriptor parsed from the filename + directory location. */\n route: { page: string; name: string; version: string };\n}\n\n//? Walk `src/` recursively. A file matches when its directory contains an\n//? `_api/` or `_sync/` segment AND the filename matches the version suffix\n//? pattern. The route's `page` is everything between `src/` and the marker\n//? segment, joined with `/`. Nested page folders are supported (e.g.\n//? `src/admin/users/_api/getUser_v1.tests.ts` → page `admin/users`).\nexport const discoverCustomTestFiles = (srcDir: string = getSrcDir()): DiscoveredTestFile[] => {\n if (!fs.existsSync(srcDir)) return [];\n const found: DiscoveredTestFile[] = [];\n const stack: string[] = [srcDir];\n //? Track visited real-paths so a cyclic symlink under `src/` can't drive an\n //? infinite walk / unbounded stack growth.\n const visited = new Set<string>();\n while (stack.length > 0) {\n const current = stack.pop();\n if (current === undefined) break;\n //? Resolve to the canonical path and skip if already seen (symlink cycle).\n const [realError, realPath] = tryCatchSync(() => fs.realpathSync(current));\n const canonical = realError || realPath === null ? current : realPath;\n if (visited.has(canonical)) continue;\n visited.add(canonical);\n //? Guard the readdir: a permission error or a directory deleted mid-walk\n //? throws synchronously and would otherwise abort the whole custom layer.\n //? Skip the unreadable directory instead.\n const [readError, entries] = tryCatchSync(() => fs.readdirSync(current, { withFileTypes: true }));\n if (readError || entries === null) continue;\n for (const entry of entries) {\n const fullPath = path.join(current, entry.name);\n //? Skip symlinked entries entirely — following them risks a cycle and\n //? a discovered test file should live in the real source tree.\n if (entry.isSymbolicLink()) continue;\n if (entry.isDirectory()) {\n stack.push(fullPath);\n continue;\n }\n if (!entry.isFile() || !entry.name.endsWith('.tests.ts')) continue;\n const parentDir = path.basename(current);\n if (parentDir !== '_api' && parentDir !== '_sync') continue;\n const pattern = parentDir === '_api' ? API_TEST_FILE_PATTERN : SYNC_SERVER_TEST_FILE_PATTERN;\n const versionMatch = pattern.exec(entry.name);\n if (!versionMatch) continue;\n const version = `v${versionMatch[1]}`;\n const namePart = entry.name.replace(parentDir === '_api' ? /_v\\d+\\.tests\\.ts$/ : /_server_v\\d+\\.tests\\.ts$/, '');\n const pageDir = path.dirname(current);\n //? Top-level `src/_api/` (no page folder) routes under the framework's\n //? `system` page — mirror that, otherwise an empty page produces the\n //? malformed URL `/api//logout/v1` and the call 404s.\n const relativePage = path.relative(srcDir, pageDir).replaceAll('\\\\', '/') || 'system';\n found.push({\n filePath: fullPath,\n kind: parentDir === '_api' ? 'api' : 'sync',\n route: { page: relativePage, name: namePart, version },\n });\n }\n }\n return found.toSorted((a, b) => a.filePath.localeCompare(b.filePath));\n};\n\nexport interface CallSyncOptions {\n /** Room the sync targets (the server requires one). */\n receiver?: string;\n /** Skip the sender's own socket in the fan-out (socket transport). */\n ignoreSelf?: boolean;\n /** Force a transport. Defaults to the project's `transport.invocation`. */\n transport?: 'socket' | 'http';\n /** Socket transport: join `receiver` before sending. Default `true`. */\n joinRoom?: boolean;\n}\n\nexport interface CallApiOptions {\n /** Force a transport. Defaults to the project's `transport.invocation`. */\n transport?: 'socket' | 'http';\n}\n\n//? Test fixture shapes. The TestContext is built per route — each per-route\n//? test file's `customTests` cases receive a context already bound to the\n//? route the file lives next to, so authors don't repeat `page/name/version`.\nexport interface TestContext {\n /**\n * Invoke the route under test. Page / name / version are baked in.\n *\n * The call takes the SAME transport the project's real client uses\n * (`transport.invocation`): Socket.io (`apiRequest`) for `'socket'` (the\n * default), the `/api/*` HTTP route for `'routed-http'`. Override per call\n * with `opts.transport`. Over HTTP, `input` travels exactly like the real\n * client sends it: as the JSON body for POST / PUT / DELETE routes, and as the\n * `__luckystack_data` query-string value for GET routes (a GET cannot carry a\n * body). Either way the server validates it as the route's `data`.\n */\n //? Two type parameters by design — `TInput` lets callers document the\n //? typed shape they're sending even though we don't validate against it\n //? here (the route's own runtime validation does). The ESLint rule\n //? otherwise flags it as \"used once\"; the documentation value is real.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- callsite-documentation use\n callApi: <TInput = unknown, TOutput = unknown>(input: TInput, opts?: CallApiOptions) => Promise<TOutput>;\n /**\n * Invoke the sync route under test over the SAME transport the project's\n * real client uses (`transport.invocation`): Socket.io for `'socket'` (the\n * default), the `/sync/*` HTTP route for `'routed-http'`. Override per call\n * with `opts.transport`. Over the socket, the case's session socket first\n * joins `opts.receiver` (like a real client joins its room before syncing);\n * pass `joinRoom: false` to test a non-member.\n */\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- callsite-documentation use\n callSync: <TInput = unknown, TOutput = unknown>(input: TInput, opts?: CallSyncOptions) => Promise<TOutput>;\n /**\n * Open a second socket connection that joins `roomCode` as a pure\n * subscriber and exposes the incoming chunk stream for assertions.\n *\n * The route is auto-derived from the test file location, so a watcher\n * only surfaces chunks for the route this test belongs to. Multiple\n * watchers per test are allowed; all are auto-closed when the test\n * case finishes (whether it passes or throws).\n */\n //? TChunk documents the expected chunk type at the call site.\n watchStream: <TChunk extends StreamChunkFrame = StreamChunkFrame>(roomCode: string) => Promise<StreamWatcher<TChunk>>;\n session: {\n login: (user?: { email?: string; id?: string; name?: string }) => Promise<{ token: string; userId: string }>;\n logout: () => Promise<void>;\n current: () => { token: string | null; userId: string | null };\n };\n prisma: ReturnType<typeof getPrismaClient>;\n expect: TestExpect;\n}\n\nexport interface TestExpect {\n eq: <T>(actual: T, expected: T, message?: string) => void;\n ok: (value: unknown, message?: string) => void;\n throws: (fn: () => Promise<unknown>, message?: string) => Promise<Error>;\n matches: (value: string, pattern: RegExp, message?: string) => void;\n}\n\nclass AssertionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AssertionError';\n }\n}\n\nconst buildExpect = (): TestExpect => ({\n eq: (actual, expected, message) => {\n const ok = Object.is(actual, expected)\n || (typeof actual === 'object' && typeof expected === 'object' && JSON.stringify(actual) === JSON.stringify(expected));\n if (!ok) {\n const prefix = message ? `${message}: ` : '';\n throw new AssertionError(`${prefix}expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`);\n }\n },\n ok: (value, message) => {\n if (!value) throw new AssertionError(message ?? `expected truthy value, got ${JSON.stringify(value)}`);\n },\n throws: async (fn, message) => {\n const [err] = await tryCatch(fn);\n if (!err) throw new AssertionError(message ?? 'expected fn to throw, but it did not');\n return err;\n },\n matches: (value, pattern, message) => {\n if (!pattern.test(value)) {\n const prefix = message ? `${message}: ` : '';\n throw new AssertionError(`${prefix}\"${value}\" does not match ${pattern}`);\n }\n },\n});\n\ninterface TestSessionState {\n token: string | null;\n userId: string | null;\n //? CSRF token minted by saveSession on login. Sent on authenticated\n //? state-changing requests so the server's CSRF middleware doesn't 403.\n csrfToken: string | null;\n //? Raw JSON body of the most recent callApi / callSync response. Surfaced in\n //? a failed case's reason so the report shows the server's actual errorCode\n //? (e.g. `api.rateLimitExceeded`) instead of only the assertion text.\n lastResponse: unknown;\n}\n\n//? Pull the server's errorCode (or non-success status) out of the last HTTP\n//? response so a failure's reason shows WHY the server rejected the call, not\n//? just the assertion text. Returns undefined for success / non-envelope bodies.\nconst extractErrorCode = (response: unknown): string | undefined => {\n if (!response || typeof response !== 'object') return undefined;\n const r = response as { errorCode?: unknown; status?: unknown };\n if (typeof r.errorCode === 'string' && r.errorCode.length > 0) return r.errorCode;\n if (typeof r.status === 'string' && r.status !== 'success') return r.status;\n return undefined;\n};\n\n//? Minimal session login: mints a session in Redis via the consumer's\n//? registered SessionAdapter (from `@luckystack/login`). Deliberately does\n//? NOT create the Prisma user record — User-table shape varies per\n//? project. If your test needs a real user row, create it via\n//? `ctx.prisma.user.create(...)` first, then pass `{ id, email }` to\n//? `session.login()`.\nconst buildSessionHelpers = (state: TestSessionState): TestContext['session'] => {\n return {\n login: async (user) => {\n const { saveSession, getSession } = await import('@luckystack/login');\n const userId = user?.id ?? `test-user-${Date.now()}-${crypto.randomBytes(8).toString('hex')}`;\n const email = user?.email ?? `test-${Date.now()}-${crypto.randomBytes(8).toString('hex')}@example.com`;\n const name = user?.name ?? 'Test User';\n const token = `test-${Date.now()}-${crypto.randomBytes(16).toString('hex')}`;\n const sessionData = {\n id: userId,\n email,\n name,\n token,\n provider: 'credentials' as const,\n };\n await saveSession(token, sessionData, true);\n state.token = token;\n state.userId = userId;\n //? Read back the CSRF token saveSession minted so authenticated\n //? state-changing requests can pass the server's CSRF middleware.\n const persisted = await getSession(token);\n state.csrfToken = persisted?.csrfToken ?? null;\n return { token, userId };\n },\n logout: async () => {\n if (!state.token) return;\n const { deleteSession } = await import('@luckystack/login');\n await deleteSession(state.token);\n state.token = null;\n state.userId = null;\n state.csrfToken = null;\n },\n current: () => ({ token: state.token, userId: state.userId }),\n };\n};\n\n//? Parse a fetch Response as JSON behind tryCatch and store the raw body on the\n//? session state. A non-JSON body (HTML 500, empty 204, truncated) would\n//? otherwise throw a raw SyntaxError with `state.lastResponse` null, hiding the\n//? server's actual output. On parse failure we keep a snippet of the text body\n//? so a failed case's reason can show what the server actually returned.\nconst parseResponse = async (response: Response, state: TestSessionState): Promise<unknown> => {\n const [textError, text] = await tryCatch(() => response.text());\n if (textError || text === null) {\n state.lastResponse = null;\n throw new Error(`failed to read response body (HTTP ${response.status}): ${textError?.message ?? 'unknown'}`);\n }\n const [parseError, parsed] = tryCatchSync(() => JSON.parse(text) as unknown);\n if (parseError) {\n const snippet = text.length > 500 ? `${text.slice(0, 500)}…` : text;\n state.lastResponse = snippet;\n throw new Error(`response was not valid JSON (HTTP ${response.status}): ${snippet}`);\n }\n state.lastResponse = parsed;\n return parsed;\n};\n\nconst resolveCallTransport = (opts: CallSyncOptions | CallApiOptions | undefined): 'socket' | 'http' =>\n opts?.transport ?? (getProjectConfig().transport.invocation === 'routed-http' ? 'http' : 'socket');\n\n/** @internal Exported for the unit test only; consumers use `TestContext.callApi`. */\nexport const buildCallApi = (\n baseUrl: string,\n routePath: string,\n state: TestSessionState,\n cookieName: string,\n //? The route's declared HTTP method (from the generated apiMethodMap). The\n //? server validates the method, so DELETE/PUT routes (e.g. logout) 405 if we\n //? always sent POST.\n method: string,\n caseSocket: CaseSyncSocket,\n): TestContext['callApi'] => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- must match TestContext['callApi'] generic signature\n return async <TInput = unknown, TOutput = unknown>(input: TInput, opts?: CallApiOptions): Promise<TOutput> => {\n //? A socket-mode client sends API calls over Socket.io (`handleApiRequest`),\n //? a different pipeline than `/api/*`; test the one real users hit.\n if (resolveCallTransport(opts) === 'socket') {\n const { socket } = await caseSocket.get(state.token);\n const outcome = await emitApiOverSocket(socket, { routePath, data: input, timeoutMs: DEFAULT_CALL_TIMEOUT_MS });\n if (outcome.kind === 'timeout') {\n state.lastResponse = null;\n throw new Error(`callApi ${routePath} failed: no socket response within ${DEFAULT_CALL_TIMEOUT_MS}ms`);\n }\n state.lastResponse = outcome.payload;\n //? The socket payload is unknown; TOutput is the caller's type contract, verified at the call site\n return outcome.payload as TOutput;\n }\n\n const path = `${baseUrl.replace(/\\/$/, '')}/api/${routePath}`;\n const headers: Record<string, string> = { 'Content-Type': 'application/json', Origin: new URL(baseUrl).origin };\n if (state.token) headers.Cookie = `${cookieName}=${state.token}`;\n if (state.csrfToken) headers[getCsrfConfig().headerName] = state.csrfToken;\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, DEFAULT_CALL_TIMEOUT_MS);\n const init: RequestInit = { method, headers, signal: controller.signal };\n //? GET / HEAD requests cannot carry a body (fetch throws), so the payload\n //? rides the query string under the same reserved key the real client\n //? uses and the server reads. Every other method sends the JSON body.\n const bodyless = method === 'GET' || method === 'HEAD';\n const url = bodyless ? buildRoutedGetUrl(path, input ?? {}) : path;\n if (!bodyless) init.body = JSON.stringify(input ?? {});\n const [fetchError, response] = await tryCatch(() => fetch(url, init));\n clearTimeout(timeoutHandle);\n if (fetchError || !response) {\n state.lastResponse = null;\n throw new Error(`callApi ${method} ${routePath} failed: ${fetchError?.message ?? 'no response'}`);\n }\n //? `parseResponse` returns `unknown`; TOutput is the caller-supplied expectation.\n //? The runtime shape is unverifiable here — the route's own validator owns that\n //? contract. The cast is required because parseResponse must return `unknown`.\n return await parseResponse(response, state) as TOutput;\n };\n};\n\n//? The case's socket, shared by socket-mode `callApi` and `callSync`. Opened\n//? lazily on the first socket call, reopened when the case's session changes\n//? (a `session.login()` after the first call), and closed with the case's\n//? watchers. Tracks joined rooms so repeated calls to the same receiver join once.\n/** @internal Exported for the unit test only. */\nexport interface CaseSyncSocket {\n get: (token: string | null) => Promise<{ socket: Socket; joinedRooms: Set<string> }>;\n close: () => void;\n}\n\nconst createCaseSyncSocket = (baseUrl: string, cookieName: string): CaseSyncSocket => {\n let current: { socket: Socket; token: string | null; joinedRooms: Set<string> } | null = null;\n const close = (): void => {\n current?.socket.disconnect();\n current = null;\n };\n return {\n get: async (token) => {\n if (current?.token === token && current.socket.connected) return current;\n close();\n const socket = await connectTestSocket({ baseUrl, token, label: 'case socket', sessionCookieName: cookieName });\n current = { socket, token, joinedRooms: new Set() };\n return current;\n },\n close,\n };\n};\n\n\nconst buildCallSync = (\n baseUrl: string,\n routePath: string,\n state: TestSessionState,\n cookieName: string,\n syncSocket: CaseSyncSocket,\n): TestContext['callSync'] => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- must match TestContext['callSync'] generic signature\n return async <TInput = unknown, TOutput = unknown>(input: TInput, opts?: CallSyncOptions): Promise<TOutput> => {\n if (resolveCallTransport(opts) === 'socket') {\n const { socket, joinedRooms } = await syncSocket.get(state.token);\n const { receiver, joinRoom = true } = opts ?? {};\n if (receiver && receiver !== 'all' && joinRoom && !joinedRooms.has(receiver)) {\n //? Best-effort: a refused join (e.g. anonymous socket) is not thrown —\n //? the sync itself then returns the server's real rejection envelope.\n const [joinError] = await tryCatch(() => joinTestRoom(socket, receiver, { label: 'callSync' }));\n if (!joinError) joinedRooms.add(receiver);\n }\n const outcome = await emitSyncOverSocket(socket, {\n routePath,\n data: input,\n receiver,\n ignoreSelf: opts?.ignoreSelf,\n timeoutMs: DEFAULT_CALL_TIMEOUT_MS,\n });\n if (outcome.kind === 'timeout') {\n state.lastResponse = null;\n throw new Error(`callSync ${routePath} failed: no socket response within ${DEFAULT_CALL_TIMEOUT_MS}ms`);\n }\n state.lastResponse = outcome.payload;\n //? The socket payload is unknown; TOutput is the caller's type contract, verified at the call site\n return outcome.payload as TOutput;\n }\n\n const url = `${baseUrl.replace(/\\/$/, '')}/sync/${routePath}`;\n const headers: Record<string, string> = { 'Content-Type': 'application/json', Origin: new URL(baseUrl).origin };\n if (state.token) headers.Cookie = `${cookieName}=${state.token}`;\n if (state.csrfToken) headers[getCsrfConfig().headerName] = state.csrfToken;\n const body = { data: input ?? {}, receiver: opts?.receiver, ignoreSelf: opts?.ignoreSelf };\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, DEFAULT_CALL_TIMEOUT_MS);\n const [fetchError, response] = await tryCatch(() => fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n signal: controller.signal,\n }));\n clearTimeout(timeoutHandle);\n if (fetchError || !response) {\n state.lastResponse = null;\n throw new Error(`callSync ${routePath} failed: ${fetchError?.message ?? 'no response'}`);\n }\n //? parseResponse returns unknown; TOutput is the caller's type contract, verified at the call site\n return await parseResponse(response, state) as TOutput;\n };\n};\n\ninterface BuiltContext {\n ctx: TestContext;\n /** Close every watcher and the sync socket opened during the case. Called by `runCustomTests` after each case. */\n closeAllWatchers: () => Promise<void>;\n /** Per-case session state — read after the run to surface the last response's errorCode. */\n state: TestSessionState;\n}\n\nconst buildContext = (\n discovery: DiscoveredTestFile,\n input: PreparedRunCustomTestsInput,\n): BuiltContext => {\n //? Default to the PROJECT's configured session cookie name (the server reads\n //? the token from this cookie). Hardcoding a name silently breaks every\n //? authenticated custom test when the project uses a different name.\n const cookieName = input.sessionCookieName ?? projectSessionCookieName();\n const routePath = `${discovery.route.page}/${discovery.route.name}/${discovery.route.version}`;\n const routeFullName = `sync/${routePath}`;\n //? API routes are invoked with their declared HTTP method; sync routes always\n //? POST. Default to POST when the map lacks the route (e.g. sync, or a map\n //? wasn't threaded through).\n const apiMethod = input.apiMethodMap?.[discovery.route.page]?.[discovery.route.name]?.[discovery.route.version] ?? 'POST';\n const state: TestSessionState = { token: null, userId: null, csrfToken: null, lastResponse: null };\n\n //? Watchers are tracked per case so a missing explicit `close()` can't\n //? leak socket B between tests.\n const watchers: StreamWatcher[] = [];\n const watchStream = async <TChunk extends StreamChunkFrame = StreamChunkFrame>(\n roomCode: string,\n ): Promise<StreamWatcher<TChunk>> => {\n const watcher = await openStreamWatcher<TChunk>({\n baseUrl: input.baseUrl,\n roomCode,\n token: state.token,\n routeFullName,\n });\n //? Track watchers under the base `StreamChunkFrame` shape; the\n //? returned reference keeps the caller-supplied generic.\n watchers.push(watcher);\n return watcher;\n };\n\n const syncSocket = createCaseSyncSocket(input.baseUrl, cookieName);\n\n const closeAllWatchers = async (): Promise<void> => {\n syncSocket.close();\n while (watchers.length > 0) {\n const watcher = watchers.pop();\n if (!watcher) continue;\n const [closeError] = await tryCatch(() => watcher.close());\n if (closeError) {\n //? Swallow — closing in cleanup must never mask the original test\n //? failure. Surface via logger if a project wires one up.\n //? (Intentionally not using getLogger here to avoid an extra import.)\n }\n }\n };\n\n const ctx: TestContext = {\n callApi: buildCallApi(input.baseUrl, routePath, state, cookieName, apiMethod, syncSocket),\n callSync: buildCallSync(input.baseUrl, routePath, state, cookieName, syncSocket),\n watchStream,\n session: buildSessionHelpers(state),\n //? Lazy: only resolves when a case actually touches `ctx.prisma`, so an\n //? orm:'none' project (no registered client) can still run DB-free custom\n //? tests — the eager resolve used to abort the whole custom-test phase.\n get prisma() {\n return getPrismaClient();\n },\n expect: buildExpect(),\n };\n\n return { ctx, closeAllWatchers, state };\n};\n\n/** @internal Environment is already prepared by `runAllTests`. */\nexport const runCustomTestsAfterEnvironmentPrepared = async (\n input: PreparedRunCustomTestsInput,\n): Promise<RunCustomTestsSummary> => {\n const srcDir = getSrcDir();\n const files = discoverCustomTestFiles(srcDir);\n const results: CustomTestResult[] = [];\n\n for (const discovery of files) {\n const routePath = `${discovery.route.page}/${discovery.route.name}/${discovery.route.version}`;\n if (input.filter && !routePath.includes(input.filter)) continue;\n\n //? Canonical-path containment guard: resolve to the real path and verify\n //? it is still inside srcDir. Protects against symlink-escaped paths or\n //? an adversarially crafted filePath that slipped through the walker.\n const [realPathErr, realFilePath] = tryCatchSync(() => fs.realpathSync(discovery.filePath));\n const resolvedPath = realPathErr ? discovery.filePath : (realFilePath ?? discovery.filePath);\n const relToSrc = path.relative(srcDir, resolvedPath);\n if (relToSrc.startsWith('..') || path.isAbsolute(relToSrc)) {\n const r: CustomTestResult = {\n routePath, caseName: '(import)', status: 'fail', durationMs: 0,\n reason: `test file path escapes srcDir and was rejected: ${discovery.filePath}`,\n };\n results.push(r);\n input.onResult?.(r);\n continue;\n }\n\n const [importError, mod] = await tryCatch(\n () => import(pathToFileURL(resolvedPath).href) as Promise<Record<string, unknown>>,\n );\n if (importError || !mod) {\n const r: CustomTestResult = {\n routePath, caseName: '(import)', status: 'fail', durationMs: 0,\n reason: `failed to import ${discovery.filePath}: ${importError?.message ?? 'unknown'}`,\n };\n results.push(r);\n input.onResult?.(r);\n continue;\n }\n\n const exported = (mod as { customTests?: unknown }).customTests;\n //? DD-TR-various — empty-stub contract (documented decision):\n //? A test file that exports an empty array (or no `customTests` export) is\n //? silently skipped, NOT emitted as a `skipped` result. Rationale: AI-generated\n //? stub files start life empty; surfacing them as \"skipped\" every run adds noise\n //? before the author fills them in. The `CustomTestResult` status union therefore\n //? stays `'pass' | 'fail' | 'xfail' | 'xpass'` — no `'skipped'` variant.\n //? If you need to track unfilled stubs, scan `discoverCustomTestFiles()` and\n //? compare against the results array.\n if (!Array.isArray(exported) || exported.length === 0) {\n continue;\n }\n\n for (const rawCase of exported) {\n const c = rawCase as CustomTestCase;\n if (typeof c.name !== 'string' || typeof c.run !== 'function') continue;\n const expectedToFail = typeof c.expectedToFail === 'string' ? c.expectedToFail : undefined;\n const built = buildContext(discovery, input);\n const started = Date.now();\n const [runError] = await tryCatch(async () => c.run(built.ctx));\n //? Always close watchers regardless of pass/fail so the next case\n //? starts clean. tryCatch on the cleanup itself prevents a teardown\n //? throw from masking the original failure.\n await tryCatch(() => built.closeAllWatchers());\n //? Best-effort session cleanup: if `session.login()` minted a Redis\n //? session but the case didn't call `session.logout()`, the token\n //? accumulates until its TTL expires. Delete it now to avoid a slow\n //? session-key leak across a full sweep run.\n const residualToken = built.state.token;\n if (residualToken) {\n await tryCatch(async () => {\n const { deleteSession } = await import('@luckystack/login');\n await deleteSession(residualToken);\n });\n }\n const durationMs = Date.now() - started;\n\n let r: CustomTestResult;\n if (runError) {\n //? Augment the assertion message with the server's actual errorCode so\n //? the report explains the rejection, not just the failed expectation.\n const errorCode = extractErrorCode(built.state.lastResponse);\n const reason = errorCode ? `${runError.message} (server: ${errorCode})` : runError.message;\n r = expectedToFail\n ? { routePath, caseName: c.name, status: 'xfail', durationMs, reason, expectedToFail }\n : { routePath, caseName: c.name, status: 'fail', durationMs, reason };\n } else {\n r = expectedToFail\n ? {\n routePath, caseName: c.name, status: 'xpass', durationMs,\n reason: `marked expectedToFail (\"${expectedToFail}\") but passed — remove the marker`,\n expectedToFail,\n }\n : { routePath, caseName: c.name, status: 'pass', durationMs };\n }\n results.push(r);\n input.onResult?.(r);\n }\n }\n\n return {\n total: results.length,\n passed: results.filter(r => r.status === 'pass').length,\n failed: results.filter(r => r.status === 'fail').length,\n xfailed: results.filter(r => r.status === 'xfail').length,\n xpassed: results.filter(r => r.status === 'xpass').length,\n results,\n };\n};\n\n//? Public direct Layer-5 path: custom modules are imported only after env files\n//? and optional secret pointers resolve. The process-wide orchestrator calls the\n//? internal prepared function above, so it neither reloads pointers nor exposes\n//? a public `skipBootstrap` flag that callers could misuse accidentally.\nexport const runCustomTests = async (input: RunCustomTestsInput): Promise<RunCustomTestsSummary> => {\n assertProjectConfigLoader(input, 'runCustomTests');\n await resolveTestEnvironment({ loadProjectConfig: input.loadProjectConfig });\n return runCustomTestsAfterEnvironmentPrepared(input);\n};\n","//? Second-socket harness for Layer 5 custom tests. Opens a separate\n//? socket.io-client connection (socket B), joins the receiver room as a\n//? pure subscriber, and exposes the broadcast/streamTo chunk stream for\n//? assertions. Without this, `ctx.callSync` only surfaces the final\n//? envelope — chunks (LLM tokens, progress ticks) emitted via\n//? `broadcastStream` / `streamTo` flow on the `sync` event channel to\n//? room members other than the request originator's socket.\n//?\n//? Keep typed generics at this test seam; do not erase stream payload types.\n//?\n//? Wire shape, sourced from `@luckystack/sync` `_shared/streamEmitters.ts`:\n//? io.to(receiver).emit('sync', { ...payload, cb, fullName, status: 'stream' })\n//? where `fullName` = `sync/<page>/<name>/<version>`. We filter incoming\n//? frames by that `fullName` so a watcher only sees chunks for the route\n//? its test file lives next to.\n\nimport { socketEventNames, tryCatch } from '@luckystack/core';\n\nimport { connectTestSocket, joinTestRoom } from './testSocket';\n\nexport interface StreamChunkFrame {\n /** Per-request correlation id stamped by the server. */\n cb?: string;\n /** Full route name (`sync/<page>/<name>/<version>`). */\n fullName?: string;\n /** Always `'stream'` for chunk frames. */\n status?: 'stream';\n /** Payload fields spread on top of the envelope. */\n [key: string]: unknown;\n}\n\nexport interface StreamWatcher<TChunk extends StreamChunkFrame = StreamChunkFrame> {\n /** Chunks received so far. Array reference is stable; entries are pushed in arrival order. */\n readonly chunks: TChunk[];\n /** Wait until `predicate(chunks)` returns true, or until timeout (default 5000ms). */\n stopAt(predicate: (chunks: TChunk[]) => boolean, timeoutMs?: number): Promise<void>;\n /** Wait until N total chunks received, or until timeout. */\n waitForCount(n: number, timeoutMs?: number): Promise<void>;\n /** Close socket B. Tests SHOULD call this in cleanup; ctx auto-closes all watchers when the test ends. */\n close(): Promise<void>;\n}\n\nexport interface OpenStreamWatcherInput {\n /** Server URL (same as `ctx`'s `baseUrl`). */\n baseUrl: string;\n /** Room to join as a pure subscriber. */\n roomCode: string;\n /** Active session token (used for `joinRoom` auth). May be null for routes that don't require auth. */\n token: string | null;\n /**\n * Resolved route name `sync/<page>/<name>/<version>` — chunk frames are\n * filtered by their `fullName` property so a watcher only surfaces chunks\n * for the route its test is bound to.\n */\n routeFullName: string;\n /** Default 5000ms — overridable per `stopAt` / `waitForCount` call. */\n defaultTimeoutMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5000;\nconst LABEL = 'watchStream';\n\nconst isStreamChunkFrame = (value: unknown): value is StreamChunkFrame =>\n typeof value === 'object' && value !== null;\n\nexport const openStreamWatcher = async <TChunk extends StreamChunkFrame = StreamChunkFrame>(\n input: OpenStreamWatcherInput,\n): Promise<StreamWatcher<TChunk>> => {\n const defaultTimeoutMs = input.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;\n const chunks: TChunk[] = [];\n const waiters: { predicate: (chunks: TChunk[]) => boolean; resolve: () => void }[] = [];\n\n //? `connectTestSocket` tears the socket down itself when the connect fails,\n //? so a failed attempt never leaks a socket the caller cannot close.\n const socket = await connectTestSocket({ baseUrl: input.baseUrl, token: input.token, label: LABEL });\n\n const handleChunk = (raw: unknown): void => {\n if (!isStreamChunkFrame(raw)) return;\n if (raw.status !== 'stream') return;\n if (raw.fullName !== input.routeFullName) return;\n //? Server emits chunk frames typed as `StreamChunkFrame` — caller's\n //? `TChunk` narrows the payload shape (e.g. `{ chunk: string }` for\n //? `streamBroadcast`). The runtime guards above confirm the wire shape;\n //? narrowing the static type is the test author's responsibility.\n const chunk = raw as TChunk;\n chunks.push(chunk);\n //? Drain waiters whose predicates are now satisfied. Iterate in reverse\n //? so we can splice without breaking indices.\n for (let i = waiters.length - 1; i >= 0; i--) {\n const waiter = waiters[i];\n if (!waiter) continue;\n if (waiter.predicate(chunks)) {\n waiters.splice(i, 1);\n waiter.resolve();\n }\n }\n };\n\n socket.on(socketEventNames.sync, handleChunk);\n\n //? A rejected / unanswered join must tear the socket (and its `sync`\n //? listener) down — the caller only tracks the watcher after this resolves.\n const [joinError] = await tryCatch(() => joinTestRoom(socket, input.roomCode, { label: LABEL }));\n if (joinError) {\n socket.off(socketEventNames.sync, handleChunk);\n socket.disconnect();\n throw joinError;\n }\n\n let closed = false;\n const close = (): Promise<void> => {\n if (closed) return Promise.resolve();\n closed = true;\n socket.off(socketEventNames.sync, handleChunk);\n //? Drain any outstanding waiters so callers don't hang past test end.\n for (const waiter of waiters) waiter.resolve();\n waiters.length = 0;\n socket.disconnect();\n return Promise.resolve();\n };\n\n const stopAt = (\n predicate: (chunks: TChunk[]) => boolean,\n timeoutMs: number = defaultTimeoutMs,\n ): Promise<void> => {\n if (predicate(chunks)) return Promise.resolve();\n return new Promise<void>((resolve, reject) => {\n let settled = false;\n const waiter = {\n predicate,\n resolve: () => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve();\n },\n };\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n const idx = waiters.indexOf(waiter);\n if (idx !== -1) waiters.splice(idx, 1);\n reject(new Error(\n `watchStream.stopAt: predicate not satisfied within ${timeoutMs}ms (received ${chunks.length} chunks)`,\n ));\n }, timeoutMs);\n waiters.push(waiter);\n });\n };\n\n const waitForCount = (n: number, timeoutMs: number = defaultTimeoutMs): Promise<void> => {\n return stopAt((current) => current.length >= n, timeoutMs);\n };\n\n return {\n chunks,\n stopAt,\n waitForCount,\n close,\n };\n};\n","//? Orchestrator — runs all five test layers (contract, auth-enforcement,\n//? rate-limit, fuzz, custom) against a running server and returns a single\n//? combined summary. Consumers call this from their own thin script that\n//? imports the generated `apiMethodMap` + `apiInputSchemas` (and, to sweep\n//? sync routes too, `syncMetaMap` + `syncInputSchemas`).\n\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { Socket } from 'socket.io-client';\nimport { clearAllRateLimits, getCsrfConfig, getProjectConfig, tryCatch, tryCatchSync } from '@luckystack/core';\n\nimport { createSampleInputFor } from './sampleInputFor';\nimport type { InputSchemaMap } from './sampleInputFor';\nimport { createTestDataMarker } from './testDataMarker';\nimport { resetServerState } from './resetServerState';\nimport { createHttpProbeTransport } from './probeRequest';\nimport type { ProbeTransport } from './probeRequest';\nimport { createSocketProbeTransport } from './socketProbeTransport';\nimport { connectTestSocket, joinTestRoom, projectSessionCookieName, releaseTestSocket } from './testSocket';\nimport { runContractTests } from './runContractTests';\nimport { runAuthEnforcementTests } from './runAuthEnforcementTests';\nimport { runRateLimitTests } from './runRateLimitTests';\nimport { runCsrfEnforcementTests } from './runCsrfEnforcementTests';\nimport { runFuzzTests } from './runFuzzTests';\nimport { runCustomTestsAfterEnvironmentPrepared } from './customTests';\nimport {\n assertProjectConfigLoader,\n resolveTestEnvironment,\n type ResolveTestEnvironmentInput,\n} from './resolveTestEnvironment';\nimport { LAYER_KEYS } from './testLayerHelpers';\nimport type { CustomTestResult, RunCustomTestsSummary } from './customTests';\nimport type { ApiMethodMap, ApiMetaMap, EndpointDescriptor, RunContractSummary, SyncMetaMap } from './types';\n\nexport interface RunAllTestsInput {\n apiMethodMap: ApiMethodMap;\n /**\n * Generated `syncMetaMap` (from `apiTypes.generated.ts`). When given, the\n * sweep layers exercise every sync route too; without it sync routes are\n * not swept (a warning says so).\n */\n syncMetaMap?: SyncMetaMap;\n /** Generated `syncInputSchemas` (from `apiInputSchemas.generated.ts`) — Zod-valid sync sample payloads. */\n syncInputSchemas?: InputSchemaMap;\n /**\n * How sync routes are probed. Defaults to the project's own\n * `transport.invocation`: `'socket'` → Socket.io (the pipeline real clients\n * hit), `'routed-http'` → the `/sync/*` HTTP route. The CSRF layer always\n * probes over HTTP — CSRF is an HTTP-only guard.\n */\n syncTransport?: 'socket' | 'http';\n /**\n * How API routes are probed. Defaults to the project's own\n * `transport.invocation`: `'socket'` → Socket.io (the `apiRequest` pipeline\n * real clients hit), `'routed-http'` → the `/api/*` HTTP route. The CSRF\n * layer always probes over HTTP — CSRF is an HTTP-only guard.\n */\n apiTransport?: 'socket' | 'http';\n /**\n * Routes designed to fail on the server (e.g. a playground route that throws\n * on purpose), matched like `skip`. The contract layer lets their shaped 5xx\n * pass; any other route answering 5xx fails it.\n */\n expectServerError?: string[];\n /**\n * Write the full `RunAllTestsSummary` as JSON to this path (relative to the\n * process cwd). Defaults to `TEST_OUTPUT_FILE`, else `test-results.json`;\n * `false` disables the file.\n */\n outputFile?: string | false;\n /**\n * Lazy loader for the consumer's default `config.ts` export. Required so\n * runAllTests cannot silently skip a configured secret manager; resolution\n * happens in THIS process before any layer or env-backed client is touched.\n */\n loadProjectConfig: NonNullable<ResolveTestEnvironmentInput['loadProjectConfig']>;\n apiMetaMap: ApiMetaMap;\n apiInputSchemas: InputSchemaMap;\n baseUrl: string;\n /** Cookie name for the session token. Defaults to the project's configured session cookie name (`projectConfig.http.sessionCookieName`, with `http.sessionCookiePrefix` applied). */\n sessionCookieName?: string;\n /**\n * Session token for sweep-layer requests that need a session: sent as the\n * session `Cookie` in cookie mode, as `Authorization: Bearer` in token mode\n * (`session.basedToken`).\n */\n authToken?: string;\n /** Endpoints to skip from the sweep layers, e.g. routes that need a file upload. */\n skip?: string[];\n /** Substring filter applied to `<page>/<name>/<version>`. */\n filter?: string;\n /** Disable individual layers (faster local iteration). */\n noSweep?: boolean;\n noFuzz?: boolean;\n noRateLimit?: boolean;\n /**\n * Disable the CSRF-enforcement layer. Defaults to ON (the layer runs) — but it\n * can only probe past the auth guard with a session, so it is a no-op unless an\n * `authToken` is supplied.\n */\n noCsrf?: boolean;\n noCustom?: boolean;\n /**\n * Token sent as `X-Test-Reset-Token` when the rate-limit layer resets the\n * shared bucket between endpoints. The server's `/_test/reset` requires\n * `TEST_RESET_TOKEN` to be set and matching — an unset/wrong token is 403, so\n * `resetBetweenEndpoints` would silently no-op. Defaults to\n * `process.env.TEST_RESET_TOKEN` so a CI run with the env set works with no\n * extra wiring; pass explicitly to override.\n */\n resetToken?: string;\n /**\n * When a reset token is available, the sweep POSTs\n * `/_test/reset?only=rateLimits` ONCE before the first layer (no bucket left\n * drained by an earlier run) and ONCE after the last (the custom layer and the\n * next run start unthrottled). Only limiter state is cleared: sessions — the\n * `authToken` session above all — survive, so `TEST_RESET_TOKEN` can be set\n * on an authenticated sweep. The reset never touches the app database.\n * Defaults to ON; set `false` to opt out. With no token the bookends are\n * skipped with a logged note (they can't run — `/_test/reset` 403s).\n */\n resetBookends?: boolean;\n}\n\nexport interface RunAllTestsSummary {\n contract?: RunContractSummary;\n auth?: RunContractSummary;\n rateLimit?: RunContractSummary;\n csrf?: RunContractSummary;\n fuzz?: RunContractSummary;\n custom?: RunCustomTestsSummary;\n totalPassed: number;\n totalFailed: number;\n}\n\n//? Decide whether the contract-sweep reset bookends should run. Returns the\n//? token to use, or null to skip. When the bookends are requested (the default)\n//? but no token is available, warn once — `/_test/reset` requires the token and\n//? would 403, so the bookends silently can't run; the sweep proceeds unguarded.\nconst resolveResetBookendToken = (input: RunAllTestsInput): string | null => {\n if (input.resetBookends === false) return null;\n const token = input.resetToken ?? process.env.TEST_RESET_TOKEN;\n if (!token) {\n console.warn(\n '[test-runner] runAllTests: reset bookends skipped — no reset token '\n + '(set TEST_RESET_TOKEN or pass resetToken/resetBookends). /_test/reset '\n + 'requires it, so the clean-slate + cleanup resets around the sweep are skipped.',\n );\n return null;\n }\n return token;\n};\n\n//? Build the session (and CSRF) headers sweep layers send when an `authToken`\n//? is supplied. In cookie-mode the server's CSRF middleware rejects POST/PUT/DELETE\n//? without a matching CSRF token, so we look up the session record and attach the\n//? token the same way customTests.ts does: call getSession(authToken) and read\n//? csrfToken off the persisted record. Token-mode sweeps send the token as\n//? `Authorization: Bearer`, the canonical token-mode credential, and no CSRF\n//? header. A Cookie there would be accepted as a fallback token while CSRF is\n//? never enforced, so the CSRF layer would fire real mutations and false-fail.\nconst buildAuthHeaders = async (input: RunAllTestsInput): Promise<Record<string, string>> => {\n const headers: Record<string, string> = {};\n if (!input.authToken) return headers;\n\n if (getProjectConfig().session.basedToken) {\n headers.Authorization = `Bearer ${input.authToken}`;\n return headers;\n }\n\n const cookieName = input.sessionCookieName ?? projectSessionCookieName();\n headers.Cookie = `${cookieName}=${input.authToken}`;\n\n //? Resolve the CSRF token lazily — import @luckystack/login at call time so\n //? projects that run the test-runner without login installed don't crash on\n //? import. Mirror the same dynamic import pattern used in customTests.ts.\n try {\n const { getSession } = await import('@luckystack/login');\n const session = await getSession(input.authToken);\n if (session?.csrfToken) {\n headers[getCsrfConfig().headerName] = session.csrfToken;\n }\n } catch (error) {\n //? @luckystack/login not installed or session lookup failed.\n //? Warn so the operator knows the sweep is running without a CSRF header\n //? (degraded mode) — the sweep continues, but CSRF-protected state-changing\n //? endpoints will be unreachable in cookie-mode.\n console.warn(\n '[test-runner] buildAuthHeaders: could not resolve CSRF token —',\n error instanceof Error ? error.message : String(error),\n '— sweep will run without the CSRF header (degraded mode).',\n );\n }\n\n return headers;\n};\n\ninterface SweepTransports {\n /** Transport the contract / auth / rate-limit / fuzz layers use for sync routes (none without sync routes). */\n sync: ProbeTransport | undefined;\n /** HTTP transport the CSRF layer uses for sync routes (CSRF is HTTP-only). */\n csrfSync: ProbeTransport | undefined;\n /** Transport the contract / auth / rate-limit / fuzz layers use for API routes; undefined = HTTP. */\n api: ProbeTransport | undefined;\n close: () => Promise<void>;\n}\n\n//? Probes follow the path real clients take (ADR 0064): the caller's override,\n//? else the project's own `transport.invocation`.\nconst resolveProbeMode = (override: 'socket' | 'http' | undefined): 'socket' | 'http' =>\n override ?? (getProjectConfig().transport.invocation === 'routed-http' ? 'http' : 'socket');\n\n//? Prepare route probing. API routes go over the socket in a socket-mode\n//? project, like the real client's `apiRequest`. Sync routes get one throwaway\n//? room per run that every sync probe targets, joined by the sweep's session\n//? so room-membership enforcement (`sync.requireRoomMembership`, default on)\n//? sees a member — exactly what a real client does before syncing. The HTTP\n//? pipeline reads membership from the session's persisted room codes, which\n//? the join writes; the socket transport additionally joins on its own socket.\n//? Without an `authToken` nothing can join, so sync probes see what an\n//? anonymous client sees.\nconst prepareSweepTransports = async (input: RunAllTestsInput, hasSyncRoutes: boolean): Promise<SweepTransports> => {\n const receiver = `lstest-room-${crypto.randomUUID()}`;\n const sessionCookieName = input.sessionCookieName ?? projectSessionCookieName();\n const syncMode = resolveProbeMode(input.syncTransport);\n const apiMode = resolveProbeMode(input.apiTransport);\n const http = createHttpProbeTransport({ syncReceiver: receiver });\n const socket = apiMode === 'socket' || (hasSyncRoutes && syncMode === 'socket')\n ? createSocketProbeTransport({ baseUrl: input.baseUrl, receiver, sessionCookieName })\n : null;\n\n let memberSocket: Socket | null = null;\n if (hasSyncRoutes && input.authToken) {\n const authToken = input.authToken;\n const [connectError, socket] = await tryCatch(() => connectTestSocket({\n baseUrl: input.baseUrl,\n token: authToken,\n label: 'sync sweep',\n sessionCookieName,\n }));\n if (connectError || !socket) {\n console.warn(`[test-runner] runAllTests: could not open a socket for the sync sweep — ${connectError?.message ?? 'no socket'}; sync probes will be rejected as non-members.`);\n } else {\n memberSocket = socket;\n const [joinError] = await tryCatch(() => joinTestRoom(socket, receiver, { label: 'sync sweep' }));\n if (joinError) console.warn(`[test-runner] runAllTests: ${joinError.message}; sync probes will be rejected as non-members.`);\n }\n }\n\n return {\n sync: hasSyncRoutes ? (syncMode === 'socket' && socket ? socket : http) : undefined,\n csrfSync: hasSyncRoutes ? http : undefined,\n api: apiMode === 'socket' && socket ? socket : undefined,\n close: async () => {\n await socket?.close?.();\n //? The member socket runs on the caller's `authToken` session — release\n //? it so presence does not log that session out after the sweep.\n if (memberSocket) await releaseTestSocket(memberSocket);\n },\n };\n};\n\n//? Run the auto-sweep layers (contract → auth → rate-limit → csrf → fuzz) in\n//? order, honoring the per-layer disable flags, and write each summary onto\n//? `summary`. Every layer receives `filter`, so a filtered run only REQUESTS\n//? the matching routes (it used to sweep everything and filter the report).\nconst runSweepLayers = async (\n input: RunAllTestsInput,\n summary: RunAllTestsSummary,\n headers: Record<string, string>,\n inputFor: (endpoint: EndpointDescriptor) => unknown,\n): Promise<void> => {\n const resetToken = input.resetToken ?? process.env.TEST_RESET_TOKEN;\n //? Reset bookend — BEFORE the sweep: no limiter bucket left drained by an\n //? earlier run. Limiter state only — the sweep's own session must survive.\n const resetBookendToken = resolveResetBookendToken(input);\n if (resetBookendToken) {\n await resetServerState({ baseUrl: input.baseUrl, token: resetBookendToken, only: ['rateLimits'] });\n }\n\n const hasSyncRoutes = Object.keys(input.syncMetaMap ?? {}).length > 0;\n const transports = await prepareSweepTransports(input, hasSyncRoutes);\n const routing = {\n apiMethodMap: input.apiMethodMap,\n syncMetaMap: input.syncMetaMap,\n baseUrl: input.baseUrl,\n skip: input.skip,\n filter: input.filter,\n };\n //? Every layer except CSRF probes over the project's own transports.\n const shared = { ...routing, syncTransport: transports.sync, apiTransport: transports.api };\n\n //? The layers must not leave sockets open when one throws.\n const [layerError] = await tryCatch(async () => {\n summary.contract = await runContractTests({\n ...shared,\n headers,\n inputFor,\n expectServerError: input.expectServerError,\n //? Reset limiter buckets between routes when a token is available: the\n //? global per-IP bucket is checked before validation, so once it fills,\n //? every later probe would be a limiter rejection, not a route test.\n resetToken,\n });\n\n summary.auth = await runAuthEnforcementTests({ ...shared, apiMetaMap: input.apiMetaMap, inputFor });\n\n if (!input.noRateLimit) {\n summary.rateLimit = await runRateLimitTests({\n ...shared,\n apiMetaMap: input.apiMetaMap,\n headers,\n inputFor,\n //? Reset the shared per-IP rate-limit bucket between endpoints so a\n //? neighbour saturating the window doesn't cause false pass/fail on the\n //? next endpoint. Requires the server to expose /_test/reset (NODE_ENV in\n //? { 'development', 'test' } AND TEST_RESET_TOKEN set + matching header).\n resetBetweenEndpoints: true,\n //? Thread the reset token (explicit input, else the TEST_RESET_TOKEN env the\n //? server validates against) — without it `/_test/reset` returns 403 and the\n //? between-endpoint reset silently never happens.\n resetToken,\n });\n }\n\n //? CSRF-enforcement layer: probes that the framework's CSRF middleware rejects\n //? state-changing authenticated requests carrying NO CSRF header. It can only\n //? reach the CSRF check past the auth guard with a valid session, so it runs\n //? only when an `authToken` is supplied (and the layer isn't disabled). Without\n //? a token there is nothing to probe — the layer is silently skipped, leaving\n //? behavior unchanged for tokenless sweeps. Token-mode sessions carry no\n //? ambient cookie credential, so the server does not enforce CSRF there:\n //? the layer is skipped with a note instead of probing mutating routes.\n if (!input.noCsrf && headers.Authorization) {\n console.log('[test-runner] runAllTests: CSRF layer skipped — token-mode sessions (session.basedToken) are not subject to CSRF enforcement.');\n } else if (!input.noCsrf && headers.Cookie) {\n summary.csrf = await runCsrfEnforcementTests({\n ...routing,\n apiMetaMap: input.apiMetaMap,\n authCookie: headers.Cookie,\n inputFor,\n //? CSRF is an HTTP-only guard: both route kinds probe over HTTP.\n syncTransport: transports.csrfSync,\n });\n }\n\n if (!input.noFuzz) {\n summary.fuzz = await runFuzzTests({ ...shared, headers, resetToken });\n }\n });\n\n await transports.close();\n\n //? Reset bookend — AFTER the sweep: the custom layer and the next run start\n //? with fresh limiter buckets.\n if (resetBookendToken) {\n await resetServerState({ baseUrl: input.baseUrl, token: resetBookendToken, only: ['rateLimits'] });\n }\n\n if (layerError) throw layerError;\n};\n\n//? Run the custom (Layer 5) sweep, after clearing the per-route rate-limit\n//? buckets the sweep layers drained. Mutates `summary` in place — identical to\n//? the prior inline block.\nconst runCustomLayer = async (input: RunAllTestsInput, summary: RunAllTestsSummary): Promise<void> => {\n //? The rate-limit layer drains per-route buckets; clear them before the\n //? custom layer so business-logic tests on low-limit routes (e.g.\n //? confirmReset, sendReset) aren't spuriously rejected with\n //? api.rateLimitExceeded. Runs in-process against the same Redis the server\n //? uses. Non-fatal if the active strategy has no clear().\n try {\n await clearAllRateLimits();\n } catch {\n //? swallow — a missing clear() is a test-quality degrade, not a crash.\n }\n const custom = await runCustomTestsAfterEnvironmentPrepared({\n baseUrl: input.baseUrl,\n sessionCookieName: input.sessionCookieName,\n filter: input.filter,\n //? Pass the generated method map so the harness invokes each API route\n //? with its declared HTTP method (e.g. logout is DELETE, not POST).\n apiMethodMap: input.apiMethodMap,\n });\n summary.custom = custom;\n};\n\n//? The ordered list of sweep-layer keys that map to `RunContractSummary`\n//? slots on `RunAllTestsSummary`. Drives `computeTotals` and the reporter so\n//? adding a new sweep layer only requires adding it here + to the summary type.\nconst SWEEP_LAYER_ORDER = [\n LAYER_KEYS.contract,\n LAYER_KEYS.auth,\n LAYER_KEYS.rateLimit,\n LAYER_KEYS.csrf,\n LAYER_KEYS.fuzz,\n] as const;\n\ntype SweepLayerKey = typeof SWEEP_LAYER_ORDER[number];\n\n//? Map from each canonical layer key to the matching summary property name on\n//? `RunAllTestsSummary`. Property names diverged from LAYER_KEYS values early\n//? (LAYER_KEYS uses display strings like `'auth-enforcement'`); keeping this\n//? map here means neither the reporter nor computeTotals need to hand-code the\n//? mapping.\nconst SWEEP_SUMMARY_PROP: Record<SweepLayerKey, keyof Pick<RunAllTestsSummary, 'contract' | 'auth' | 'rateLimit' | 'csrf' | 'fuzz'>> = {\n [LAYER_KEYS.contract]: 'contract',\n [LAYER_KEYS.auth]: 'auth',\n [LAYER_KEYS.rateLimit]: 'rateLimit',\n [LAYER_KEYS.csrf]: 'csrf',\n [LAYER_KEYS.fuzz]: 'fuzz',\n};\n\n//? Iterate every sweep layer + the custom layer to sum passed/failed.\n//? DD-TR-various — xpass exit-code policy (documented decision):\n//? `xpass` (a case marked `expectedToFail` that unexpectedly passes) is NOT\n//? counted in `totalFailed`. Rationale: an xpass is a positive signal — the\n//? known issue is gone and the marker just needs removing. Treating it as a\n//? failure would make CI red when a bug is fixed, which is backwards.\n//? It IS shown prominently in the summary line and the \"Unexpectedly passed\"\n//? section so the stale marker can't rot silently behind a green run.\n//? To opt into strict mode (xpass = CI failure), inspect `summary.custom?.xpassed`\n//? in your own script and `process.exit(1)` when it is > 0.\nconst computeTotals = (summary: RunAllTestsSummary): void => {\n let passed = 0;\n let failed = 0;\n for (const key of SWEEP_LAYER_ORDER) {\n const s = summary[SWEEP_SUMMARY_PROP[key]];\n passed += s?.passed ?? 0;\n failed += s?.failed ?? 0;\n }\n summary.totalPassed = passed + (summary.custom?.passed ?? 0);\n summary.totalFailed = failed + (summary.custom?.failed ?? 0);\n};\n\nexport const runAllTests = async (input: RunAllTestsInput): Promise<RunAllTestsSummary> => {\n assertProjectConfigLoader(input, 'runAllTests');\n //? The live server resolves its own env, but Layer-5 tests may use ctx.prisma\n //? or Redis directly in THIS process. Resolve before importing custom tests or\n //? constructing those clients, otherwise DATABASE_URL_V<n> reaches Prisma raw.\n await resolveTestEnvironment({ loadProjectConfig: input.loadProjectConfig });\n\n const summary: RunAllTestsSummary = { totalPassed: 0, totalFailed: 0 };\n\n //? Warn when a non-empty apiMethodMap is paired with an empty apiMetaMap.\n //? The auth-enforcement and rate-limit layers both use the meta map to decide\n //? which routes require login and which have declared rate limits. An absent\n //? or stale map silently skips every auth/rate-limit check — the run looks\n //? green but provides no real coverage.\n if (!input.noSweep) {\n const hasRoutes = Object.keys(input.apiMethodMap).length > 0;\n const hasMeta = Object.keys(input.apiMetaMap).length > 0;\n if (hasRoutes && !hasMeta) {\n console.warn(\n '[test-runner] runAllTests: apiMetaMap is empty but apiMethodMap has routes. '\n + 'The auth-enforcement and rate-limit layers will skip every route. '\n + 'Pass the generated apiMetaMap to enable those checks.',\n );\n }\n //? Sync routes are only swept when the generated syncMetaMap is passed —\n //? say so instead of letting them silently go without transport coverage.\n if (!input.syncMetaMap) {\n console.warn(\n '[test-runner] runAllTests: no syncMetaMap passed — sync routes are NOT swept. '\n + 'Pass the generated syncMetaMap (+ syncInputSchemas) to cover them.',\n );\n }\n }\n\n const headers = await buildAuthHeaders(input);\n //? One marker per run so every generated string this sweep posts is tagged\n //? with the same recognizable `lstest_<uuid>_` prefix (finding #98).\n const inputFor = createSampleInputFor({\n apiInputSchemas: input.apiInputSchemas,\n syncInputSchemas: input.syncInputSchemas,\n stringPrefix: createTestDataMarker(),\n });\n\n if (!input.noSweep) {\n await runSweepLayers(input, summary, headers, inputFor);\n }\n\n if (!input.noCustom) {\n await runCustomLayer(input, summary);\n }\n\n computeTotals(summary);\n writeSummaryFile(summary, input.outputFile);\n\n return summary;\n};\n\n//? Machine-readable results, so CI or an AI agent can read pass/fail + the\n//? per-result reasons without scraping the terminal. A write failure is\n//? reported, never thrown — the summary is still returned to the caller.\nconst writeSummaryFile = (summary: RunAllTestsSummary, outputFile: string | false | undefined): void => {\n if (outputFile === false) return;\n const target = path.resolve(outputFile ?? process.env.TEST_OUTPUT_FILE ?? 'test-results.json');\n const [writeError] = tryCatchSync(() => {\n fs.writeFileSync(target, JSON.stringify(summary, null, 2), 'utf8');\n });\n if (writeError) {\n console.warn(`[test-runner] runAllTests: could not write ${target} — ${writeError.message}`);\n return;\n }\n console.log(`[test-runner] full results written to ${target}`);\n};\n\n//? ── Colored, list-based reporting ───────────────────────────────────────\n//? Honors NO_COLOR / FORCE_COLOR (https://no-color.org) and falls back to\n//? plain text when stdout is not a TTY (piped to a file / CI log).\nconst useColor = ((): boolean => {\n if (process.env.NO_COLOR) return false;\n if (process.env.FORCE_COLOR) return true;\n return process.stdout.isTTY;\n})();\nconst paint = (code: string, text: string): string => (useColor ? `\\u001B[${code}m${text}\\u001B[0m` : text);\nconst green = (t: string): string => paint('32', t);\nconst red = (t: string): string => paint('31', t);\nconst yellow = (t: string): string => paint('33', t);\nconst dim = (t: string): string => paint('2', t);\nconst bold = (t: string): string => paint('1', t);\n\ninterface ReportRow {\n label: string;\n reason?: string;\n}\n\n//? DD-TR-various — leaky summary union tightened:\n//? `LayerSummary` is the minimal shape the reporter cares about — the fields\n//? every layer summary has in common. The extra-field helpers discriminate via\n//? `in` to stay type-safe without widening callers to `unknown`.\ninterface LayerSummary {\n total: number;\n passed: number;\n failed: number;\n}\n\nconst xfailOf = (s: LayerSummary | undefined): number => {\n if (s === undefined || !('xfailed' in s)) return 0;\n const v = (s as Record<string, unknown>).xfailed;\n return typeof v === 'number' ? v : 0;\n};\nconst skippedOf = (s: LayerSummary | undefined): number => {\n if (s === undefined || !('skipped' in s)) return 0;\n const v = (s as Record<string, unknown>).skipped;\n return typeof v === 'number' ? v : 0;\n};\n\n//? Per-layer headline: green \"X/Y passed\" when clean, red \"Z failed\" when not,\n//? plus dim xfail / skipped counts. This is the \"14/20 in groen, 6/20 in rood\"\n//? the report leads with.\nconst formatLayerLine = (label: string, s: LayerSummary | undefined): string => {\n if (!s) return ` ${dim('–')} ${label.padEnd(18)} ${dim('(layer skipped)')}`;\n const clean = s.failed === 0;\n const mark = clean ? green('✓') : red('✗');\n const head = clean\n ? green(`${s.passed}/${s.total} passed`)\n : `${green(`${s.passed}/${s.total} passed`)} ${red(`${s.failed}/${s.total} failed`)}`;\n const extras: string[] = [];\n const xf = xfailOf(s);\n const sk = skippedOf(s);\n if (xf) extras.push(yellow(`${xf} expected-fail`));\n if (sk) extras.push(dim(`${sk} skipped`));\n const extra = extras.length > 0 ? ` ${dim('·')} ${extras.join(dim(', '))}` : '';\n return ` ${mark} ${label.padEnd(18)} ${head}${extra}`;\n};\n\nconst sweepFailRows = (s: RunContractSummary | undefined): ReportRow[] =>\n (s?.results ?? [])\n .filter((r) => r.status === 'fail')\n .map((r) => ({\n label: `${r.endpoint.page}/${r.endpoint.name}/${r.endpoint.version}`,\n reason: r.errorCode ? `${r.reason ?? 'failed'} [${r.errorCode}]` : r.reason,\n }));\n\nconst sweepSkipRows = (layer: string, s: RunContractSummary | undefined): ReportRow[] =>\n (s?.results ?? [])\n .filter((r) => r.status === 'skipped')\n .map((r) => ({\n label: `${layer}: ${r.endpoint.page}/${r.endpoint.name}/${r.endpoint.version}`,\n reason: r.reason,\n }));\n\nconst customRows = (s: RunCustomTestsSummary | undefined, status: CustomTestResult['status']): ReportRow[] =>\n (s?.results ?? [])\n .filter((r) => r.status === status)\n .map((r) => ({ label: `${r.routePath} :: ${r.caseName}`, reason: r.reason }));\n\nconst printSection = (title: (t: string) => string, heading: string, rows: ReportRow[], bullet: string): void => {\n if (rows.length === 0) return;\n console.log('');\n console.log(title(bold(`${heading} (${rows.length})`)));\n for (const row of rows) {\n console.log(` ${title(bullet)} ${row.label}`);\n if (row.reason) console.log(` ${dim(row.reason)}`);\n }\n};\n\nexport const logRunAllSummary = (summary: RunAllTestsSummary): void => {\n console.log('');\n\n //? Print a headline line for every sweep layer (ordered) then the custom layer.\n for (const key of SWEEP_LAYER_ORDER) {\n console.log(formatLayerLine(key, summary[SWEEP_SUMMARY_PROP[key]]));\n }\n console.log(formatLayerLine(LAYER_KEYS.custom, summary.custom));\n\n //? Real failures across every layer — these MUST be fixed (a green test that\n //? went red, or a wrong test). Sweep failures + un-marked custom failures.\n const failed: ReportRow[] = [\n ...SWEEP_LAYER_ORDER.flatMap((key) => sweepFailRows(summary[SWEEP_SUMMARY_PROP[key]])),\n ...customRows(summary.custom, 'fail'),\n ];\n printSection(red, 'Failed — must be fixed (real bugs / wrong tests)', failed, '✗');\n\n //? Known, accepted failures — red is OK here; they are tracked, not regressions.\n printSection(yellow, 'Expected failures — known issues, allowed to fail', customRows(summary.custom, 'xfail'), '⚠');\n\n //? Stale markers — these passed but are still flagged expectedToFail.\n printSection(yellow, 'Unexpectedly passed — remove the expectedToFail marker', customRows(summary.custom, 'xpass'), '?');\n\n //? Skipped with reason — not run in this mode (login-gated, high rate-limit, …).\n const skipped: ReportRow[] = SWEEP_LAYER_ORDER.flatMap((key) =>\n sweepSkipRows(key, summary[SWEEP_SUMMARY_PROP[key]]),\n );\n printSection(dim, 'Skipped — not run (with reason)', skipped, '–');\n\n //? Sum xfail and skipped across all layers (sweep + custom) in one pass.\n let totalXfail = 0;\n let totalSkipped = 0;\n for (const key of SWEEP_LAYER_ORDER) {\n const s = summary[SWEEP_SUMMARY_PROP[key]];\n totalXfail += xfailOf(s);\n totalSkipped += skippedOf(s);\n }\n totalXfail += xfailOf(summary.custom);\n totalSkipped += skippedOf(summary.custom);\n\n //? `xpass` (a case still marked expectedToFail that now passes) is NOT a\n //? failure, so it doesn't move the exit code — but it MUST be visible in the\n //? final line, otherwise a stale marker rots silently behind a green run.\n const totalXpass = summary.custom?.xpassed ?? 0;\n\n console.log('');\n const parts = [\n green(`${summary.totalPassed} passed`),\n summary.totalFailed > 0 ? red(`${summary.totalFailed} failed`) : dim('0 failed'),\n totalXfail > 0 ? yellow(`${totalXfail} expected-fail`) : dim('0 expected-fail'),\n totalXpass > 0 ? yellow(`${totalXpass} unexpectedly-passed`) : dim('0 unexpectedly-passed'),\n totalSkipped > 0 ? yellow(`${totalSkipped} skipped`) : dim('0 skipped'),\n ];\n console.log(`Summary: ${parts.join(dim(' · '))}`);\n console.log(dim(' legend: ✗ red = must fix · ⚠ yellow = known/allowed · – skipped (not run)'));\n};\n","//? Extension registry for the test runner. Consumers plug in custom test\n//? layers, realistic-payload fixtures, and result reporters without\n//? forking the framework.\n//?\n//? Three slots:\n//?\n//? 1. Test layers — additional checks per endpoint (CORS enforcement,\n//? business rules, custom auth schemes, multi-tenant isolation).\n//? 2. Fixtures — realistic input payloads (valid + invalid) per typeKey\n//? so the fuzz layer can use them before falling back to schema-random.\n//? 3. Reporters — per-result and per-summary callbacks + optional\n//? webhook URL the runner POSTs the batch summary to.\n//?\n//? All slots are append-only at registration time; clearing requires\n//? `resetTestExtensionsForTests()` (kept out of the production surface\n//? to prevent accidental wipes from a misbehaving plugin).\n\nexport interface TestLayerInput {\n /** Resolved route name (`api/billing/getInvoice/v1` or `sync/chat/send/v1`). */\n endpoint: string;\n /** Method when applicable, undefined for sync endpoints. */\n method?: string;\n /** Bearer token / session token to use for auth-aware checks. */\n authToken?: string;\n /**\n * A Zod-valid sample `data` payload for this route — generated from the\n * route's input schema when `runRegisteredLayers` received the generated\n * schemas (or an `inputFor`), `{}` otherwise. The same payload the built-in\n * contract sweep sends, so a layer can exercise the route past validation.\n */\n sampleInput?: unknown;\n}\n\nexport interface TestLayerResult {\n passed: boolean;\n /** Short reason key — i18n-style errorCode when failed. */\n message?: string;\n /** Optional extras for the reporter (latency, response sample, ...). */\n metadata?: Record<string, unknown>;\n}\n\nexport interface TestLayer {\n name: string;\n run: (input: TestLayerInput) => Promise<TestLayerResult> | TestLayerResult;\n}\n\nexport interface TestFixture<TPayload = unknown> {\n valid: TPayload[];\n invalid: TPayload[];\n}\n\nexport interface TestResult {\n layer: string;\n endpoint: string;\n passed: boolean;\n message?: string;\n durationMs?: number;\n metadata?: Record<string, unknown>;\n}\n\nexport interface TestSummary {\n totalLayers: number;\n totalEndpoints: number;\n passed: number;\n failed: number;\n results: TestResult[];\n}\n\nexport interface TestReporter {\n /** Called once per individual TestResult as the runner progresses. */\n onResult?: (result: TestResult) => void | Promise<void>;\n /** Called once at the end with the aggregated summary. */\n onSummary?: (summary: TestSummary) => void | Promise<void>;\n /** Optional webhook the runner POSTs the summary to. */\n webhookUrl?: string;\n webhookAuth?: { type: 'bearer'; token: string };\n}\n\nconst layers = new Map<string, TestLayer>();\nconst fixtures = new Map<string, TestFixture>();\nlet reporter: TestReporter | null = null;\n\n/**\n * Register a custom test layer. Re-registering the same name replaces\n * the previous entry. Use for CORS enforcement, business-rule checks,\n * custom auth schemes, multi-tenant isolation, GDPR data-flow checks.\n */\nexport const registerTestLayer = (layer: TestLayer): void => {\n layers.set(layer.name, layer);\n};\n\nexport const listTestLayers = (): TestLayer[] => [...layers.values()];\n\n/**\n * Register a realistic payload fixture for a specific type key. The fuzz\n * layer prefers these over schema-random generated inputs when a fixture\n * exists for the endpoint's input type.\n *\n * `valid` payloads are used to verify happy-path behavior; `invalid`\n * payloads probe the validator's error responses.\n */\nexport const registerTestFixture = <TPayload = unknown>(typeKey: string, fixture: TestFixture<TPayload>): void => {\n fixtures.set(typeKey, fixture);\n};\n\nexport const getTestFixture = (typeKey: string): TestFixture | undefined => fixtures.get(typeKey);\n\n/**\n * Register a test reporter. Set `null` to unregister. The runner calls\n * `onResult` per individual result as the run progresses and `onSummary`\n * once at the end. When `webhookUrl` is set, the runner additionally\n * POSTs the JSON-serialised summary to that URL.\n */\nexport const registerTestReporter = (newReporter: TestReporter | null): void => {\n reporter = newReporter;\n};\n\nexport const getTestReporter = (): TestReporter | null => reporter;\n\nexport const resetTestExtensionsForTests = (): void => {\n layers.clear();\n fixtures.clear();\n reporter = null;\n};\n","//? Runs every layer registered via `registerTestLayer` against every endpoint\n//? in the given `apiMethodMap`, fans each result to the registered reporter\n//? (`onResult` / `onSummary`), and — when the reporter declares a `webhookUrl`\n//? — POSTs the JSON-serialised summary to it.\n//?\n//? Built-in sweep layers (contract/auth/rate-limit/fuzz) are NOT run here and\n//? `runAllTests` does NOT call this function automatically. The extension registry\n//? is a coordination surface for consumer test harnesses: read the layers with\n//? `listTestLayers()`, then call `runRegisteredLayers({ apiMethodMap, authToken })`\n//? from your own aggregator after the built-in sweeps finish. See\n//? docs/extension-hooks.md for the full lifecycle and examples.\n\nimport { tryCatch, tryCatchSync } from '@luckystack/core';\n\nimport { listTestLayers, getTestReporter } from './extensionRegistry';\nimport { createSampleInputFor } from './sampleInputFor';\nimport type { InputSchemaMap } from './sampleInputFor';\nimport { createTestDataMarker } from './testDataMarker';\nimport { selectEndpoints, shouldSkip } from './testLayerHelpers';\nimport type { TestResult, TestSummary } from './extensionRegistry';\nimport type { ApiMethodMap, EndpointDescriptor, SyncMetaMap } from './types';\n\nexport interface RunRegisteredLayersInput {\n apiMethodMap: ApiMethodMap;\n /** Generated `syncMetaMap`. When given, layers also run against every sync route. */\n syncMetaMap?: SyncMetaMap;\n /** Session/bearer token forwarded to each layer's `run({ authToken })`. */\n authToken?: string;\n /**\n * Endpoints to skip — matched against `<page>/<name>` (version-agnostic) and\n * `<page>/<name>/<version>`, same convention as the built-in sweeps.\n */\n skip?: string[];\n /** Substring filter on `<page>/<name>/<version>`, same as the built-in sweeps. */\n filter?: string;\n /** Generated `apiInputSchemas` — source of each API route's `sampleInput`. */\n apiInputSchemas?: InputSchemaMap;\n /** Generated `syncInputSchemas` — source of each sync route's `sampleInput`. */\n syncInputSchemas?: InputSchemaMap;\n /** Custom sample factory; wins over the schema maps when given. */\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n}\n\n//? Whether a webhook URL targets a loopback host (localhost / 127.0.0.0/8 / ::1).\n//? Plaintext http to a non-loopback target leaks the route inventory (+ bearer)\n//? over the wire, so we warn — but never block, since the URL is consumer-self-\n//? registered.\nconst isLoopbackHost = (host: string): boolean =>\n host === 'localhost'\n || host === '::1'\n || host === '[::1]'\n || /^127\\.\\d+\\.\\d+\\.\\d+$/.test(host);\n\n//? POST the summary to the reporter's webhook, if one is configured. Best-effort\n//? — a webhook failure must never fail the test run.\nconst postWebhook = async (summary: TestSummary): Promise<void> => {\n const reporter = getTestReporter();\n const webhookUrl = reporter?.webhookUrl;\n if (!reporter || !webhookUrl) return;\n //? Warn (don't block) when forwarding the summary — which carries endpoint\n //? paths, error codes and reporter metadata verbatim — plaintext to a\n //? non-loopback host. Mirrors the plaintext-target caution the other network\n //? helpers carry.\n const [, parsedUrl] = tryCatchSync(() => new URL(webhookUrl));\n if (parsedUrl?.protocol === 'http:' && !isLoopbackHost(parsedUrl.hostname)) {\n console.warn(\n `[test-runner] reporter.webhookUrl is plaintext http to a non-loopback host (${parsedUrl.hostname}); `\n + 'the summary (endpoint paths, error codes, metadata) and any bearer token are sent unencrypted.',\n );\n }\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (reporter.webhookAuth?.type === 'bearer') headers.Authorization = `Bearer ${reporter.webhookAuth.token}`;\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, 10_000);\n await tryCatch(() => fetch(webhookUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify(summary),\n signal: controller.signal,\n }));\n clearTimeout(timeoutHandle);\n};\n\nexport const runRegisteredLayers = async (input: RunRegisteredLayersInput): Promise<TestSummary> => {\n const layers = listTestLayers();\n const endpoints = selectEndpoints(input);\n const reporter = getTestReporter();\n const skip = input.skip ?? [];\n const results: TestResult[] = [];\n //? Same sample source as the built-in sweeps, tagged with a run-unique marker\n //? so rows a layer creates are identifiable (finding #98).\n const inputFor = input.inputFor ?? createSampleInputFor({\n apiInputSchemas: input.apiInputSchemas,\n syncInputSchemas: input.syncInputSchemas,\n stringPrefix: createTestDataMarker(),\n });\n\n for (const layer of layers) {\n for (const endpoint of endpoints) {\n if (shouldSkip(endpoint, skip)) continue;\n const started = Date.now();\n const [runError, layerResult] = await tryCatch(async () =>\n layer.run({\n endpoint: endpoint.fullPath,\n method: endpoint.kind === 'sync' ? undefined : endpoint.method,\n authToken: input.authToken,\n sampleInput: inputFor(endpoint),\n }));\n const durationMs = Date.now() - started;\n\n const result: TestResult = runError || !layerResult\n ? {\n layer: layer.name,\n endpoint: endpoint.fullPath,\n passed: false,\n message: runError?.message ?? 'layer returned no result',\n durationMs,\n }\n : {\n layer: layer.name,\n endpoint: endpoint.fullPath,\n passed: layerResult.passed,\n message: layerResult.message,\n durationMs,\n metadata: layerResult.metadata,\n };\n\n results.push(result);\n await tryCatch(async () => reporter?.onResult?.(result));\n }\n }\n\n const summary: TestSummary = {\n totalLayers: layers.length,\n totalEndpoints: endpoints.length,\n passed: results.filter(r => r.passed).length,\n failed: results.filter(r => !r.passed).length,\n results,\n };\n\n await tryCatch(async () => reporter?.onSummary?.(summary));\n await postWebhook(summary);\n\n return summary;\n};\n"],"mappings":";AAMA,IAAM,qBAAqB,oBAAI,IAAgB,CAAC,OAAO,QAAQ,OAAO,QAAQ,CAAC;AAE/E,IAAM,eAAe,CAAC,QAAmC;AAIvD,QAAM,QAAQ,IAAI,YAAY;AAC9B,aAAW,KAAK,oBAAoB;AAClC,QAAI,MAAM,MAAO,QAAO;AAAA,EAC1B;AACA,SAAO;AACT;AAEO,IAAM,gBAAgB,CAAC,iBAAqD;AACjF,QAAM,YAAkC,CAAC;AACzC,aAAW,CAAC,MAAM,OAAO,KAAK,OAAO,QAAQ,YAAY,GAAG;AAC1D,QAAI,CAAC,QAAS;AACd,eAAW,CAAC,MAAM,UAAU,KAAK,OAAO,QAAQ,OAAO,GAAG;AACxD,UAAI,CAAC,WAAY;AACjB,iBAAW,CAAC,SAAS,SAAS,KAAK,OAAO,QAAQ,UAAU,GAAG;AAC7D,YAAI,cAAc,OAAW;AAC7B,cAAM,SAAS,aAAa,SAAS;AACrC,YAAI,CAAC,QAAQ;AAGX,kBAAQ,KAAK,6CAA6C,IAAI,IAAI,IAAI,IAAI,OAAO,gCAA2B,SAAS,mBAAmB;AACxI;AAAA,QACF;AACA,kBAAU,KAAK;AAAA,UACb;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,UAAU,OAAO,IAAI,IAAI,IAAI,IAAI,OAAO;AAAA,QAC1C,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAUO,IAAM,oBAAoB,CAAC,YAA8E;AAC9G,QAAM,YAAkC,CAAC;AACzC,aAAW,CAAC,MAAM,OAAO,KAAK,OAAO,QAAQ,OAAO,GAAG;AACrD,QAAI,CAAC,QAAS;AACd,eAAW,CAAC,MAAM,UAAU,KAAK,OAAO,QAAQ,OAAO,GAAG;AACxD,UAAI,CAAC,WAAY;AACjB,iBAAW,CAAC,SAAS,IAAI,KAAK,OAAO,QAAQ,UAAU,GAAG;AACxD,YAAI,SAAS,OAAW;AACxB,kBAAU,KAAK;AAAA,UACb;AAAA,UACA;AAAA,UACA;AAAA,UACA,QAAQ;AAAA,UACR,UAAU,QAAQ,IAAI,IAAI,IAAI,IAAI,OAAO;AAAA,UACzC,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;;;ACjEA,SAAS,mBAAmB,gBAAgB;AAsBrC,IAAM,YAAY,OAAO,UAA4D;AAC1F,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,gBAAgB,WAAW,MAAM;AAAE,eAAW,MAAM;AAAA,EAAG,GAAG,MAAM,gBAAgB;AAMtF,QAAM,QAAQ,MAAM,WAAW;AAC/B,QAAM,MAAM,SAAS,MAAM,SAAS,SAAY,kBAAkB,MAAM,KAAK,MAAM,IAAI,IAAI,MAAM;AAEjG,QAAM,CAAC,YAAY,QAAQ,IAAI,MAAM,SAAS,MAAM,MAAM,KAAK;AAAA,IAC7D,QAAQ,MAAM;AAAA,IACd,SAAS;AAAA,MACP,gBAAgB;AAAA;AAAA;AAAA,MAGhB,UAAU,IAAI,IAAI,MAAM,OAAO,EAAE;AAAA,MACjC,GAAG,MAAM;AAAA,IACX;AAAA,IACA,MAAM,QAAQ,SAAY,KAAK,UAAU,MAAM,IAAI;AAAA,IACnD,QAAQ,WAAW;AAAA,EACrB,CAAC,CAAC;AACF,eAAa,aAAa;AAE1B,MAAI,cAAc,CAAC,SAAU,QAAO;AAEpC,QAAM,CAAC,EAAE,MAAM,IAAI,MAAM;AAAA,IACvB,YAAY,MAAM,SAAS,KAAK;AAAA,EAClC;AAEA,SAAO,EAAE,YAAY,SAAS,QAAQ,QAAQ,UAAU,KAAK;AAC/D;AA+BO,IAAM,2BAA2B,CAAC,UAAqC,CAAC,OAAuB;AAAA,EACpG,MAAM;AAAA,EACN,MAAM,CAAC,YAAY,UAAU;AAAA,IAC3B,KAAK,GAAG,QAAQ,QAAQ,QAAQ,OAAO,EAAE,CAAC,IAAI,QAAQ,SAAS,QAAQ;AAAA,IACvE,QAAQ,QAAQ,SAAS;AAAA,IACzB,SAAS,QAAQ;AAAA,IACjB,MAAM,QAAQ,SAAS,SAAS,SAC5B,EAAE,MAAM,QAAQ,MAAM,UAAU,QAAQ,aAAa,IACrD,QAAQ;AAAA,IACZ,SAAS,QAAQ;AAAA,IACjB,kBAAkB,QAAQ;AAAA,EAC5B,CAAC;AACH;AAGO,IAAM,wBAAwC,yBAAyB;;;ACvFvE,IAAM,aAAa;AAAA,EACxB,UAAU;AAAA,EACV,MAAM;AAAA,EACN,WAAW;AAAA,EACX,MAAM;AAAA,EACN,MAAM;AAAA,EACN,QAAQ;AACV;AAKO,IAAM,yBAAyB,oBAAI,IAAgB,CAAC,QAAQ,OAAO,QAAQ,CAAC;AAG5E,IAAM,WAAW,CAAC,aACvB,GAAG,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI,SAAS,OAAO;AAGhD,IAAM,gBAAgB,CAAC,UAA8B,WAC1D,CAAC,UAAU,SAAS,QAAQ,EAAE,SAAS,MAAM;AAgBxC,IAAM,kBAAkB,CAAC,UAAsD;AAAA,EACpF,GAAG,cAAc,MAAM,YAAY;AAAA,EACnC,GAAI,MAAM,cAAc,kBAAkB,MAAM,WAAW,IAAI,CAAC;AAClE,EAAE,OAAO,CAAC,aAAa,cAAc,UAAU,MAAM,MAAM,CAAC;AAGrD,IAAM,aAAa,CACxB,UACA,YACA,gBACkB,SAAS,SAAS,SAAS,eAAe,CAAC,IAAI;AAM5D,IAAM,eAAe,CAC1B,UACA,eACA,kBACoB,SAAS,SAAS,SAAS,gBAAgB,iBAAiB;AAO3E,IAAM,oBAAoB,CAAC,YAChC,QAAQ,SAAS,UAAU,SAAS,aAAa;AAI5C,IAAM,oBAAoB,CAAC,cAChC,WAAW,SAAS,oBAAoB,KAAK;AAOxC,IAAM,aAAa,CAAC,UAA8B,SAA4B;AACnF,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,QAAM,YAAY,SAAS,QAAQ;AACnC,QAAM,cAAc,GAAG,SAAS,IAAI,IAAI,SAAS,IAAI;AACrD,SAAO,KAAK,SAAS,SAAS,KAAK,KAAK,SAAS,WAAW;AAC9D;AAGO,IAAM,gBAAgB,CAAC,SAAuB,aAA0C;AAC7F,QAAM,OAAO,QAAQ,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI,SAAS,OAAO;AAOvE,SAAO,MAAM,MAAM,SAAS;AAC9B;AAMO,IAAM,qBAAqB,CAAC,SAAuB,aAA0C;AAClG,QAAM,OAAO,QAAQ,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI,SAAS,OAAO;AAEvE,UAAQ,MAAM,MAAM,SAAS,WAAW,MAAM,MAAM,iBAAiB;AACvE;AAGO,IAAM,eAAe,CAAC,SAAuB,aAClD,QAAQ,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI,SAAS,OAAO,MAAM;AAM3D,IAAM,eAAe,CAAC,SAAuB,aAAgD;AAClG,QAAM,OAAO,QAAQ,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI,SAAS,OAAO;AACvE,MAAI,CAAC,QAAQ,KAAK,cAAc,SAAS,KAAK,cAAc,OAAW,QAAO;AAC9E,SAAO,KAAK;AACd;AAGO,IAAM,mBAAmB,CAAC,aAAwD;AAAA,EACvF,OAAO,QAAQ;AAAA,EACf,QAAQ,QAAQ,OAAO,OAAK,EAAE,WAAW,MAAM,EAAE;AAAA,EACjD,QAAQ,QAAQ,OAAO,OAAK,EAAE,WAAW,MAAM,EAAE;AAAA,EACjD,SAAS,QAAQ,OAAO,OAAK,EAAE,WAAW,SAAS,EAAE;AAAA,EACrD;AACF;;;AChJA,IAAM,6BAA6B;AAiCnC,IAAM,sBAAsB,oBAAI,IAAI;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAID,IAAM,qBAAqB,oBAAI,IAAI;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAUM,IAAM,mBAAmB,OAAO,UAA4D;AACjG,QAAM,EAAE,UAAU,QAAQ,IAAI;AAC9B,QAAM,OAAO,MAAM,WAAW,MAAM,SAAS,QAAQ,IAAI,CAAC;AAC1D,QAAM,UAAU,KAAK,IAAI;AAEzB,QAAM,SAAS,OAAO,MAAM,aAAa,uBAAuB,KAAK;AAAA,IACnE;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,MAAM;AAAA,IACf,kBAAkB,MAAM,oBAAoB;AAAA,EAC9C,CAAC;AAED,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,YAAY,KAAK,IAAI,IAAI;AAAA,IAC3B;AAAA,EACF;AAEA,QAAM,EAAE,YAAY,OAAO,IAAI;AAC/B,QAAM,aAAa,KAAK,IAAI,IAAI;AAEhC,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA,gBAAgB;AAAA,MAChB,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,WAAW,aAAa,OAAO,WAAW,SAAS;AAC5D,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA,gBAAgB;AAAA,MAChB,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,WAAW,WAAW,CAAC,OAAO,WAAW;AAClD,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA,gBAAgB;AAAA,MAChB,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAIA,MAAI,kBAAkB,OAAO,SAAS,GAAG;AACvC,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA,gBAAgB;AAAA,MAChB,WAAW,OAAO;AAAA,MAClB,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,cAAc,UAAa,oBAAoB,IAAI,OAAO,SAAS,GAAG;AAC/E,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA,gBAAgB;AAAA,MAChB,WAAW,OAAO;AAAA,MAClB,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,gBAAiB,eAAe,UAAa,cAAc,OAC3D,OAAO,cAAc,UAAa,mBAAmB,IAAI,OAAO,SAAS;AAC/E,MAAI,eAAe;AACjB,WAAO;AAAA,MACL;AAAA,MACA,QAAQ,MAAM,mBAAmB,SAAS;AAAA,MAC1C;AAAA,MACA,gBAAgB;AAAA,MAChB,WAAW,OAAO;AAAA,MAClB,QAAQ,MAAM,mBACV,wDACA;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AAOA,QAAM,iBAAiB,SAAS,QAC3B,OAAO,SAAS,YAChB,CAAC,MAAM,QAAQ,IAAI,KACnB,OAAO,KAAK,IAAI,EAAE,SAAS;AAChC,MAAI,MAAM,YAAY,kBAAkB,OAAO,WAAW,SAAS;AACjE,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA,gBAAgB;AAAA,MAChB,WAAW,OAAO;AAAA,MAClB,QAAQ,kEAAkE,OAAO,aAAa,SAAS;AAAA,MACvG;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA,gBAAgB,OAAO;AAAA,IACvB,WAAW,OAAO;AAAA,IAClB;AAAA,EACF;AACF;;;ACjMA,SAAS,kBAAkB,uBAAuB,YAAAA,iBAAgB;AAYlE,IAAMC,8BAA6B;AAc5B,IAAM,mBAAmB,OAAO,EAAE,SAAS,OAAO,kBAAkB,KAAK,MAA+C;AAC7H,QAAM,WAAW,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC,GAAG,iBAAiB,EAAE,KAAK,iBAAiB;AAC1F,QAAM,MAAM,QAAQ,KAAK,SAAS,IAC9B,GAAG,QAAQ,IAAI,IAAI,gBAAgB,EAAE,CAAC,qBAAqB,GAAG,KAAK,KAAK,GAAG,EAAE,CAAC,EAAE,SAAS,CAAC,KAC1F;AACJ,QAAM,UAAkC,EAAE,gBAAgB,mBAAmB;AAC7E,MAAI,MAAO,SAAQ,oBAAoB,IAAI;AAI3C,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,gBAAgB,WAAW,MAAM;AAAE,eAAW,MAAM;AAAA,EAAG,GAAG,oBAAoBA,2BAA0B;AAC9G,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAMD,UAAS,MAAM,MAAM,KAAK,EAAE,QAAQ,QAAQ,SAAS,QAAQ,WAAW,OAAO,CAAC,CAAC;AACjH,eAAa,aAAa;AAC1B,MAAI,SAAS,CAAC,SAAU,QAAO;AAC/B,SAAO,SAAS;AAClB;;;ACFO,IAAM,mBAAmB,OAAO,UAA8D;AACnG,QAAM,YAAY,gBAAgB,KAAK;AACvC,QAAM,OAAO,MAAM,QAAQ,CAAC;AAC5B,QAAM,UAAiC,CAAC;AAExC,aAAW,YAAY,WAAW;AAChC,QAAI,WAAW,UAAU,IAAI,GAAG;AAC9B,YAAM,UAA+B;AAAA,QACnC;AAAA,QACA,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,QAAQ;AAAA,MACV;AACA,cAAQ,KAAK,OAAO;AACpB,YAAM,WAAW,OAAO;AACxB;AAAA,IACF;AAEA,QAAI,MAAM,YAAY;AACpB,YAAM,iBAAiB,EAAE,SAAS,MAAM,SAAS,OAAO,MAAM,YAAY,MAAM,CAAC,YAAY,EAAE,CAAC;AAAA,IAClG;AAIA,UAAM,SAAS,MAAM,iBAAiB;AAAA,MACpC;AAAA,MACA,SAAS,MAAM;AAAA,MACf,UAAU,MAAM;AAAA,MAChB,SAAS,MAAM;AAAA,MACf,WAAW,aAAa,UAAU,MAAM,eAAe,MAAM,YAAY;AAAA,MACzE,kBAAkB,WAAW,UAAU,MAAM,qBAAqB,CAAC,CAAC;AAAA,IACtE,CAAC;AACD,YAAQ,KAAK,MAAM;AACnB,UAAM,WAAW,MAAM;AAAA,EACzB;AAEA,SAAO,iBAAiB,OAAO;AACjC;;;ACzEA,IAAME,8BAA6B;AAQnC,IAAM,uBAAuB,oBAAI,IAAI,CAAC,iBAAiB,gBAAgB,CAAC;AAiBjE,IAAM,0BAA0B,OACrC,UACiC;AACjC,QAAM,EAAE,UAAU,QAAQ,IAAI;AAC9B,QAAM,OAAO,MAAM,WAAW,MAAM,SAAS,QAAQ,IAAI,CAAC;AAC1D,QAAM,UAAU,KAAK,IAAI;AAKzB,QAAM,SAAS,OAAO,MAAM,aAAa,uBAAuB,KAAK;AAAA,IACnE;AAAA,IACA;AAAA,IACA;AAAA,IACA,kBAAkB,MAAM,oBAAoBA;AAAA,EAC9C,CAAC;AAED,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,YAAY,KAAK,IAAI,IAAI;AAAA,IAC3B;AAAA,EACF;AAEA,QAAM,EAAE,YAAY,OAAO,IAAI;AAC/B,QAAM,aAAa,KAAK,IAAI,IAAI;AAEhC,MAAI,QAAQ,WAAW,WAAW;AAChC,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA,gBAAgB;AAAA,MAChB,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,QAAQ,WAAW,SAAS;AAC9B,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA,gBAAgB;AAAA,MAChB,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,cAAc,UAAa,CAAC,qBAAqB,IAAI,OAAO,SAAS,GAAG;AACjF,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA,gBAAgB;AAAA,MAChB,WAAW,OAAO;AAAA,MAClB,QAAQ,yCAAyC,CAAC,GAAG,oBAAoB,EAAE,KAAK,KAAK,CAAC,cAAc,OAAO,aAAa,WAAW;AAAA,MACnI;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA,gBAAgB;AAAA,IAChB,WAAW,OAAO;AAAA,IAClB;AAAA,EACF;AACF;;;AC/DO,IAAM,0BAA0B,OACrC,UACgC;AAChC,QAAM,YAAY,gBAAgB,KAAK;AACvC,QAAM,OAAO,MAAM,QAAQ,CAAC;AAC5B,QAAM,UAAiC,CAAC;AAExC,aAAW,YAAY,WAAW;AAChC,UAAM,UAAU,WAAW,UAAU,MAAM,YAAY,MAAM,WAAW;AAOxE,QAAI,CAAC,aAAa,SAAS,QAAQ,GAAG;AAMpC,YAAM,UAA+B;AAAA,QACnC;AAAA,QACA,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,QAAQ,MAAM,SAAS,SAAS,SAAS,gBAAgB,YAAY;AAAA,MACvE;AACA,cAAQ,KAAK,OAAO;AACpB,YAAM,WAAW,OAAO;AACxB;AAAA,IACF;AAEA,QAAI,CAAC,mBAAmB,SAAS,QAAQ,GAAG;AAI1C;AAAA,IACF;AAEA,QAAI,WAAW,UAAU,IAAI,GAAG;AAC9B,YAAM,UAA+B;AAAA,QACnC;AAAA,QACA,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,QAAQ;AAAA,MACV;AACA,cAAQ,KAAK,OAAO;AACpB,YAAM,WAAW,OAAO;AACxB;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,wBAAwB;AAAA,MAC3C;AAAA,MACA,SAAS,MAAM;AAAA,MACf,UAAU,MAAM;AAAA,MAChB,WAAW,aAAa,UAAU,MAAM,eAAe,MAAM,YAAY;AAAA,IAC3E,CAAC;AACD,YAAQ,KAAK,MAAM;AACnB,UAAM,WAAW,MAAM;AAAA,EACzB;AAEA,SAAO,iBAAiB,OAAO;AACjC;;;AC/FA,IAAMC,8BAA6B;AAInC,IAAM,uBAAuB,CAAC,aAC5B,SAAS,SAAS,SAAS,2BAA2B;AAmBxD,IAAM,0BAA0B,oBAAI,IAAI;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAsBM,IAAM,oBAAoB,OAAO,UAA6D;AACnG,QAAM,EAAE,UAAU,SAAS,UAAU,IAAI;AACzC,QAAM,OAAO,MAAM,WAAW,MAAM,SAAS,QAAQ,IAAI,CAAC;AAC1D,QAAM,UAAU,KAAK,IAAI;AACzB,QAAM,mBAAmB,MAAM,oBAAoBA;AACnD,QAAM,oBAAoB,qBAAqB,QAAQ;AACvD,QAAM,YAAY,MAAM,aAAa;AAErC,QAAM,OAAO,MAAM,UAAU,KAAK;AAAA,IAChC;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,MAAM;AAAA,IACf;AAAA,EACF,CAAC;AAQD,WAAS,IAAI,GAAG,IAAI,WAAW,KAAK,GAAG;AACrC,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,CAAC,QAAS;AACd,UAAM,YAAY,QAAQ,QAAQ;AAClC,QAAI,aAAa,wBAAwB,IAAI,SAAS,GAAG;AACvD,aAAO;AAAA,QACL;AAAA,QACA,QAAQ;AAAA,QACR,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,QAAQ,iBAAiB,IAAI,CAAC,0BAA0B,SAAS;AAAA,MACnE;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAAQ,MAAM,KAAK;AACzB,QAAM,aAAa,KAAK,IAAI,IAAI;AAEhC,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,EAAE,YAAY,OAAO,IAAI;AAE/B,MAAI,QAAQ,WAAW,WAAW,OAAO,cAAc,mBAAmB;AACxE,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA,gBAAgB;AAAA,MAChB,WAAW,OAAO;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,iBACJ,QAAQ,WAAW,aAAa,QAAQ,WAAW,UAAU,OAAO,SAAS;AAC/E,SAAO;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA,WAAW,QAAQ;AAAA,IACnB,QAAQ,aAAa,iBAAiB,gBAAgB,YAAY,CAAC,YAAY,QAAQ,UAAU,MAAM,IAAI,QAAQ,aAAa,QAAQ;AAAA,IACxI;AAAA,EACF;AACF;;;ACpEO,IAAM,oBAAoB,OAC/B,UACgC;AAChC,QAAM,YAAY,gBAAgB,KAAK;AACvC,QAAM,OAAO,MAAM,QAAQ,CAAC;AAC5B,QAAM,eAAe,MAAM,sBAAsB;AAOjD,QAAM,uBAAuB,kBAAkB,MAAM,OAAO;AAC5D,QAAM,UAAiC,CAAC;AAExC,aAAW,YAAY,WAAW;AAChC,UAAM,UAAU,WAAW,UAAU,MAAM,YAAY,MAAM,WAAW;AACxE,UAAM,YAAY,aAAa,SAAS,QAAQ;AAChD,QAAI,cAAc,KAAM;AAExB,QAAI,CAAC,wBAAwB,cAAc,SAAS,QAAQ,GAAG;AAC7D,YAAM,UAA+B;AAAA,QACnC;AAAA,QACA,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,QAAQ;AAAA,MACV;AACA,cAAQ,KAAK,OAAO;AACpB,YAAM,WAAW,OAAO;AACxB;AAAA,IACF;AAOA,QAAI,wBAAwB,uBAAuB,IAAI,SAAS,MAAM,GAAG;AACvE,YAAM,UAA+B;AAAA,QACnC;AAAA,QACA,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,QAAQ;AAAA,MACV;AACA,cAAQ,KAAK,OAAO;AACpB,YAAM,WAAW,OAAO;AACxB;AAAA,IACF;AAEA,QAAI,YAAY,cAAc;AAC5B,YAAM,UAA+B;AAAA,QACnC;AAAA,QACA,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,QAAQ,aAAa,SAAS,+BAA+B,YAAY;AAAA,MAC3E;AACA,cAAQ,KAAK,OAAO;AACpB,YAAM,WAAW,OAAO;AACxB;AAAA,IACF;AAEA,QAAI,WAAW,UAAU,IAAI,GAAG;AAC9B,YAAM,UAA+B;AAAA,QACnC;AAAA,QACA,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,QAAQ;AAAA,MACV;AACA,cAAQ,KAAK,OAAO;AACpB,YAAM,WAAW,OAAO;AACxB;AAAA,IACF;AAEA,QAAI,MAAM,uBAAuB;AAG/B,YAAM,iBAAiB,EAAE,SAAS,MAAM,SAAS,OAAO,MAAM,YAAY,MAAM,CAAC,YAAY,EAAE,CAAC;AAAA,IAClG;AAEA,UAAM,SAAS,MAAM,kBAAkB;AAAA,MACrC;AAAA,MACA,SAAS,MAAM;AAAA,MACf;AAAA,MACA,UAAU,MAAM;AAAA,MAChB,SAAS,MAAM;AAAA,MACf,WAAW,aAAa,UAAU,MAAM,eAAe,MAAM,YAAY;AAAA,IAC3E,CAAC;AACD,YAAQ,KAAK,MAAM;AACnB,UAAM,WAAW,MAAM;AAAA,EACzB;AAEA,SAAO,iBAAiB,OAAO;AACjC;;;ACrJA,IAAMC,8BAA6B;AAKnC,IAAM,8BAA8B;AACpC,IAAM,+BAA+B;AA8B9B,IAAM,0BAA0B,OACrC,UACiC;AACjC,QAAM,EAAE,UAAU,QAAQ,IAAI;AAK9B,MAAI,SAAS,WAAW,OAAO;AAC7B,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ,sCAAsC,SAAS,MAAM;AAAA,MAC7D,YAAY;AAAA,IACd;AAAA,EACF;AACA,QAAM,YAAY,MAAM,aAAa;AACrC,MAAI,UAAU,SAAS,QAAQ;AAC7B,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ,uDAAuD,UAAU,IAAI;AAAA,MAC7E,YAAY;AAAA,IACd;AAAA,EACF;AACA,QAAM,OAAO,MAAM,WAAW,MAAM,SAAS,QAAQ,IAAI,CAAC;AAC1D,QAAM,UAAU,KAAK,IAAI;AACzB,QAAM,oBAAoB,MAAM,qBAAqB;AACrD,QAAM,qBAAqB,MAAM,sBAAsB;AAIvD,QAAM,SAAS,MAAM,UAAU,KAAK;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,EAAE,QAAQ,MAAM,WAAW;AAAA,IACpC,kBAAkB,MAAM,oBAAoBA;AAAA,EAC9C,CAAC;AAED,MAAI,CAAC,QAAQ;AACX,WAAO,EAAE,UAAU,QAAQ,QAAQ,QAAQ,8BAA8B,YAAY,KAAK,IAAI,IAAI,QAAQ;AAAA,EAC5G;AAEA,QAAM,EAAE,YAAY,OAAO,IAAI;AAC/B,QAAM,aAAa,KAAK,IAAI,IAAI;AAEhC,MAAI,QAAQ,WAAW,WAAW;AAChC,WAAO;AAAA,MACL;AAAA,MAAU,QAAQ;AAAA,MAAQ;AAAA,MAAY,gBAAgB;AAAA,MACtD,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,QAAQ,WAAW,WAAW,OAAO,cAAc,mBAAmB;AACxE,UAAM,iBACJ,QAAQ,WAAW,aAAa,QAAQ,WAAW,UAAU,OAAO,SAAS;AAC/E,WAAO;AAAA,MACL;AAAA,MAAU,QAAQ;AAAA,MAAQ;AAAA,MAAY;AAAA,MAAgB,WAAW,QAAQ;AAAA,MACzE,QAAQ,4BAA4B,iBAAiB,aAAa,QAAQ,UAAU,MAAM,IAAI,QAAQ,aAAa,QAAQ;AAAA,MAC3H;AAAA,IACF;AAAA,EACF;AAEA,MAAI,uBAAuB,SAAS,eAAe,oBAAoB;AACrE,WAAO;AAAA,MACL;AAAA,MAAU,QAAQ;AAAA,MAAQ;AAAA,MAAY,gBAAgB;AAAA,MAAS,WAAW,OAAO;AAAA,MACjF,QAAQ,iBAAiB,kBAAkB,8BAA8B,UAAU;AAAA,MACnF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,UAAU,QAAQ,QAAQ,YAAY,gBAAgB,SAAS,WAAW,OAAO,WAAW,WAAW;AAClH;;;AClEO,IAAM,0BAA0B,OACrC,UACgC;AAChC,QAAM,YAAY,gBAAgB,KAAK;AACvC,QAAM,OAAO,MAAM,QAAQ,CAAC;AAC5B,QAAM,UAAiC,CAAC;AAExC,aAAW,YAAY,WAAW;AAChC,UAAM,UAAU,WAAW,UAAU,MAAM,YAAY,MAAM,WAAW;AAIxE,QAAI,CAAC,aAAa,SAAS,QAAQ,EAAG;AACtC,QAAI,CAAC,cAAc,SAAS,QAAQ,EAAG;AACvC,QAAI,CAAC,uBAAuB,IAAI,SAAS,MAAM,EAAG;AAElD,QAAI,WAAW,UAAU,IAAI,GAAG;AAC9B,YAAM,UAA+B;AAAA,QACnC;AAAA,QACA,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,QAAQ;AAAA,MACV;AACA,cAAQ,KAAK,OAAO;AACpB,YAAM,WAAW,OAAO;AACxB;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,wBAAwB;AAAA,MAC3C;AAAA,MACA,SAAS,MAAM;AAAA,MACf,YAAY,MAAM;AAAA,MAClB,UAAU,MAAM;AAAA,MAChB,kBAAkB,MAAM;AAAA,MACxB,mBAAmB,MAAM;AAAA,MACzB,oBAAoB,MAAM;AAAA,MAC1B,WAAW,aAAa,UAAU,MAAM,aAAa;AAAA,IACvD,CAAC;AACD,YAAQ,KAAK,MAAM;AACnB,UAAM,WAAW,MAAM;AAAA,EACzB;AAEA,SAAO,iBAAiB,OAAO;AACjC;;;ACtFA,IAAMC,8BAA6B;AAMnC,IAAM,gBAAoC;AAAA,EACxC;AAAA,EACA,CAAC;AAAA,EACD,CAAC,GAAG,GAAG,CAAC;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,IAAI,OAAO,GAAM,EAAE,EAAE,EAAE,EAAE;AAAA;AAAA;AAAA;AAAA,EAIhE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE;AAAA,EAClC,EAAE,KAAK,MAAM,OAAO,QAAW,OAAO,OAAO,IAAI;AACnD;AAYO,IAAM,eAAe,OAAO,UAAwD;AACzF,QAAM,UAAU,KAAK,IAAI;AACzB,QAAM,mBAAmB,MAAM,oBAAoBA;AACnD,QAAM,YAAY,MAAM,aAAa;AAErC,aAAW,CAAC,OAAO,OAAO,KAAK,cAAc,QAAQ,GAAG;AACtD,UAAM,SAAS,MAAM,UAAU,KAAK;AAAA,MAClC,UAAU,MAAM;AAAA,MAChB,SAAS,MAAM;AAAA,MACf,MAAM;AAAA,MACN,SAAS,MAAM;AAAA,MACf;AAAA,IACF,CAAC;AACD,UAAM,aAAa,KAAK,IAAI,IAAI;AAEhC,QAAI,CAAC,QAAQ;AACX,aAAO;AAAA,QACL,UAAU,MAAM;AAAA,QAChB,QAAQ;AAAA,QACR,QAAQ,kDAAkD,KAAK,UAAU,OAAO,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,QAC9F;AAAA,MACF;AAAA,IACF;AAQA,QAAI,CAAC,OAAO,UAAW,OAAO,OAAO,WAAW,aAAa,OAAO,OAAO,WAAW,SAAU;AAC9F,aAAO;AAAA,QACL,UAAU,MAAM;AAAA,QAChB,QAAQ;AAAA,QACR,YAAY,OAAO;AAAA,QACnB,QAAQ,gDAAgD,KAAK,UAAU,OAAO,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,QAC5F;AAAA,MACF;AAAA,IACF;AAKA,QAAI,kBAAkB,OAAO,OAAO,SAAS,GAAG;AAC9C,aAAO;AAAA,QACL,UAAU,MAAM;AAAA,QAChB,QAAQ;AAAA,QACR,YAAY,OAAO;AAAA,QACnB,WAAW,OAAO,OAAO;AAAA,QACzB,QAAQ,sBAAsB,OAAO,KAAK,CAAC,OAAO,OAAO,cAAc,MAAM,CAAC;AAAA,QAC9E;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO,OAAO,WAAW,WAAW,OAAO,OAAO,OAAO,cAAc,UAAU;AACnF,aAAO;AAAA,QACL,UAAU,MAAM;AAAA,QAChB,QAAQ;AAAA,QACR,YAAY,OAAO;AAAA,QACnB,QAAQ,sDAAsD,KAAK,UAAU,OAAO,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,QAClG;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU,MAAM;AAAA,IAChB,QAAQ;AAAA,IACR,YAAY,KAAK,IAAI,IAAI;AAAA,EAC3B;AACF;;;ACvEO,IAAM,eAAe,OAAO,UAA0D;AAC3F,QAAM,YAAY,gBAAgB,KAAK;AACvC,QAAM,OAAO,MAAM,QAAQ,CAAC;AAO5B,QAAM,uBAAuB,kBAAkB,MAAM,OAAO;AAC5D,QAAM,UAAiC,CAAC;AAExC,aAAW,YAAY,WAAW;AAChC,QAAI,wBAAwB,uBAAuB,IAAI,SAAS,MAAM,GAAG;AACvE,YAAM,UAA+B;AAAA,QACnC;AAAA,QACA,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,QAAQ;AAAA,MACV;AACA,cAAQ,KAAK,OAAO;AACpB,YAAM,WAAW,OAAO;AACxB;AAAA,IACF;AAEA,QAAI,WAAW,UAAU,IAAI,GAAG;AAC9B,YAAM,UAA+B;AAAA,QACnC;AAAA,QACA,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,QAAQ;AAAA,MACV;AACA,cAAQ,KAAK,OAAO;AACpB,YAAM,WAAW,OAAO;AACxB;AAAA,IACF;AAEA,QAAI,MAAM,YAAY;AACpB,YAAM,iBAAiB,EAAE,SAAS,MAAM,SAAS,OAAO,MAAM,YAAY,MAAM,CAAC,YAAY,EAAE,CAAC;AAAA,IAClG;AAEA,UAAM,SAAS,MAAM,aAAa;AAAA,MAChC;AAAA,MACA,SAAS,MAAM;AAAA,MACf,SAAS,MAAM;AAAA,MACf,WAAW,aAAa,UAAU,MAAM,eAAe,MAAM,YAAY;AAAA,IAC3E,CAAC;AACD,YAAQ,KAAK,MAAM;AACnB,UAAM,WAAW,MAAM;AAAA,EACzB;AAEA,SAAO,iBAAiB,OAAO;AACjC;;;ACzEA,SAAS,YAAAC,iBAAgB;;;ACDzB,SAAS,MAAM,4BAAoC;AACnD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,oBAAAC;AAAA,EACA;AAAA,EACA,YAAAC;AAAA,OACK;AAEP,IAAM,6BAA6B;AACnC,IAAM,0BAA0B;AAMzB,IAAM,2BAA2B,MAAc;AACpD,QAAM,EAAE,KAAK,IAAID,kBAAiB;AAClC,SAAO,6BAA6B,KAAK,mBAAmB,KAAK,mBAAmB,EAAE;AACxF;AAKA,IAAI,uBAAuB;AACpB,IAAM,oBAAoB,MAAc;AAC7C,0BAAwB;AACxB,SAAO;AACT;AAaA,IAAM,iBAAiB,CAAC,QAAgB,OAAe,cAAqC;AAC1F,SAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,QAAI,OAAO,WAAW;AACpB,cAAQ;AACR;AAAA,IACF;AACA,UAAM,QAAQ,WAAW,MAAM;AAC7B,cAAQ;AACR,aAAO,IAAI,MAAM,GAAG,KAAK,qCAAqC,SAAS,IAAI,CAAC;AAAA,IAC9E,GAAG,SAAS;AACZ,UAAM,YAAY,MAAY;AAC5B,cAAQ;AACR,cAAQ;AAAA,IACV;AACA,UAAM,iBAAiB,CAAC,QAAqB;AAC3C,cAAQ;AACR,aAAO,IAAI,MAAM,GAAG,KAAK,0BAAqB,IAAI,OAAO,EAAE,CAAC;AAAA,IAC9D;AACA,UAAM,UAAU,MAAY;AAC1B,mBAAa,KAAK;AAClB,aAAO,IAAI,WAAW,SAAS;AAC/B,aAAO,IAAI,iBAAiB,cAAc;AAAA,IAC5C;AACA,WAAO,GAAG,WAAW,SAAS;AAC9B,WAAO,GAAG,iBAAiB,cAAc;AAAA,EAC3C,CAAC;AACH;AAOO,IAAM,oBAAoB,OAAO,UAAmD;AAOzF,QAAM,oBAAoB,MAAM,qBAAqB,yBAAyB;AAC9E,QAAM,SAAiB,qBAAqB,MAAM,SAAS;AAAA,IACzD,YAAY,CAAC,WAAW;AAAA,IACxB,UAAU;AAAA,IACV,cAAc;AAAA,IACd,MAAM,MAAM,QAAQ,EAAE,OAAO,MAAM,MAAM,IAAI;AAAA,IAC7C,cAAc,MAAM,QAAQ,EAAE,QAAQ,GAAG,iBAAiB,IAAI,MAAM,KAAK,GAAG,IAAI;AAAA,EAClF,CAAC;AAED,QAAM,CAAC,YAAY,IAAI,MAAMC;AAAA,IAC3B,MAAM,eAAe,QAAQ,MAAM,OAAO,MAAM,oBAAoB,0BAA0B;AAAA,EAChG;AACA,MAAI,cAAc;AAChB,WAAO,WAAW;AAClB,UAAM;AAAA,EACR;AACA,SAAO;AACT;AAEA,IAAM,qBAAqB;AAcpB,IAAM,oBAAoB,OAAO,WAAkC;AACxE,MAAI,CAAC,OAAO,UAAW;AACvB,QAAM,IAAI,QAAc,CAAC,YAAY;AACnC,UAAM,QAAQ,WAAW,MAAM;AAC7B,aAAO,IAAI,cAAc,YAAY;AACrC,cAAQ;AAAA,IACV,GAAG,kBAAkB;AACrB,UAAM,eAAe,MAAY;AAC/B,mBAAa,KAAK;AAClB,cAAQ;AAAA,IACV;AACA,WAAO,KAAK,cAAc,YAAY;AACtC,WAAO,KAAK,iBAAiB,qBAAqB;AAAA,EACpD,CAAC;AACD,SAAO,WAAW;AACpB;AAMO,IAAM,eAAe,CAC1B,QACA,MACA,YACkB;AAClB,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,gBAAgB,kBAAkB;AACxC,QAAM,YAAY,+BAA+B,aAAa;AAC9D,SAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,UAAM,QAAQ,WAAW,MAAM;AAC7B,aAAO,IAAI,WAAW,KAAK;AAC3B,aAAO,IAAI,MAAM,GAAG,QAAQ,KAAK,gCAAgC,SAAS,IAAI,CAAC;AAAA,IACjF,GAAG,SAAS;AACZ,UAAM,QAAQ,CAAC,YAA2B;AACxC,mBAAa,KAAK;AAClB,aAAO,IAAI,WAAW,KAAK;AAC3B,UAAI,OAAO,YAAY,YAAY,YAAY,QAAS,QAAiC,WAAW,SAAS;AAC3G,cAAM,YAAa,QAAoC;AACvD,eAAO,IAAI,MAAM,GAAG,QAAQ,KAAK,8BAAyB,OAAO,cAAc,WAAW,YAAY,SAAS,EAAE,CAAC;AAClH;AAAA,MACF;AACA,cAAQ;AAAA,IACV;AACA,WAAO,GAAG,WAAW,KAAK;AAC1B,WAAO,KAAK,iBAAiB,UAAU,EAAE,OAAO,MAAM,cAAc,CAAC;AAAA,EACvE,CAAC;AACH;AAiBA,IAAM,oBAAoB,CACxB,QACA,YACA,MACA,cAC6B,IAAI,QAAyB,CAAC,YAAY;AACvE,QAAM,QAAQ,WAAW,MAAM;AAC7B,WAAO,IAAI,YAAY,UAAU;AACjC,YAAQ,EAAE,MAAM,UAAU,CAAC;AAAA,EAC7B,GAAG,SAAS;AACZ,QAAM,aAAa,CAAC,YAA2B;AAC7C,iBAAa,KAAK;AAClB,WAAO,IAAI,YAAY,UAAU;AACjC,YAAQ,EAAE,MAAM,YAAY,QAAQ,CAAC;AAAA,EACvC;AACA,SAAO,GAAG,YAAY,UAAU;AAChC,OAAK;AACP,CAAC;AAMM,IAAM,qBAAqB,CAAC,QAAgB,UAAmD;AACpG,QAAM,gBAAgB,kBAAkB;AACxC,SAAO,kBAAkB,QAAQ,2BAA2B,aAAa,GAAG,MAAM;AAChF,WAAO,KAAK,iBAAiB,MAAM;AAAA,MACjC,MAAM,QAAQ,MAAM,SAAS;AAAA;AAAA,MAE7B,MAAM,MAAM,SAAS,SAAY,CAAC,IAAI,MAAM;AAAA;AAAA;AAAA;AAAA,MAI5C,IAAI,MAAM;AAAA,MACV,UAAU,MAAM;AAAA,MAChB;AAAA,MACA,YAAY,MAAM,cAAc;AAAA,IAClC,CAAC;AAAA,EACH,GAAG,MAAM,SAAS;AACpB;AAcO,IAAM,oBAAoB,CAAC,QAAgB,UAAkD;AAClG,QAAM,gBAAgB,kBAAkB;AACxC,SAAO,kBAAkB,QAAQ,0BAA0B,aAAa,GAAG,MAAM;AAC/E,WAAO,KAAK,iBAAiB,YAAY;AAAA,MACvC,MAAM,OAAO,MAAM,SAAS;AAAA;AAAA,MAE5B,MAAM,MAAM,SAAS,SAAY,CAAC,IAAI,MAAM;AAAA,MAC5C;AAAA,IACF,CAAC;AAAA,EACH,GAAG,MAAM,SAAS;AACpB;;;AD7OA,IAAM,QAAQ;AAWd,IAAM,wBAAwB,CAAC,cAAkC,eAAsC;AACrG,MAAI,CAAC,aAAc,QAAO;AAC1B,aAAW,QAAQ,aAAa,MAAM,GAAG,GAAG;AAC1C,UAAM,CAAC,SAAS,GAAG,IAAI,IAAI,KAAK,KAAK,EAAE,MAAM,GAAG;AAChD,QAAI,YAAY,cAAc,KAAK,SAAS,EAAG,QAAO,KAAK,KAAK,GAAG;AAAA,EACrE;AACA,SAAO;AACT;AAGA,IAAM,mBAAmB,CAAC,SAA6C,eAAsC;AAC3G,QAAM,cAAc,sBAAsB,SAAS,QAAQ,UAAU;AACrE,MAAI,YAAa,QAAO;AACxB,QAAM,gBAAgB,SAAS;AAC/B,SAAO,eAAe,WAAW,SAAS,IAAI,cAAc,MAAM,UAAU,MAAM,IAAI;AACxF;AAIA,IAAM,kBAAkB,CAAC,YAAoC;AAC3D,MAAI,OAAO,YAAY,YAAY,YAAY,KAAM,QAAO,EAAE,QAAQ,KAAK;AAC3E,QAAM,SAAS;AACf,SAAO;AAAA,IACL,GAAI,OAAO,OAAO,eAAe,WAAW,EAAE,YAAY,OAAO,WAAW,IAAI,CAAC;AAAA,IACjF,QAAQ;AAAA,MACN,QAAQ,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS;AAAA,MAC5D,WAAW,OAAO,OAAO,cAAc,WAAW,OAAO,YAAY;AAAA,IACvE;AAAA,EACF;AACF;AAEO,IAAM,6BAA6B,CAAC,YAAyD;AAGlG,QAAM,UAAU,oBAAI,IAAoC;AAExD,QAAM,YAAY,CAAC,UAAiD;AAClE,UAAM,MAAM,SAAS;AACrB,UAAM,WAAW,QAAQ,IAAI,GAAG;AAChC,QAAI,SAAU,QAAO;AACrB,UAAM,WAAW,YAAoC;AACnD,YAAM,CAAC,cAAc,MAAM,IAAI,MAAMC,UAAS,MAAM,kBAAkB;AAAA,QACpE,SAAS,QAAQ;AAAA,QACjB;AAAA,QACA,OAAO;AAAA,QACP,mBAAmB,QAAQ;AAAA,MAC7B,CAAC,CAAC;AACF,UAAI,gBAAgB,CAAC,OAAQ,QAAO;AAGpC,UAAI,MAAO,OAAMA,UAAS,MAAM,aAAa,QAAQ,QAAQ,UAAU,EAAE,OAAO,MAAM,CAAC,CAAC;AACxF,aAAO;AAAA,IACT,GAAG;AACH,YAAQ,IAAI,KAAK,OAAO;AACxB,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,OAAO,YAAkE;AACpF,UAAM,SAAS,MAAM,UAAU,iBAAiB,QAAQ,SAAS,QAAQ,iBAAiB,CAAC;AAC3F,QAAI,CAAC,OAAQ,QAAO;AACpB,UAAM,YAAY,GAAG,QAAQ,SAAS,IAAI,IAAI,QAAQ,SAAS,IAAI,IAAI,QAAQ,SAAS,OAAO;AAC/F,UAAM,UAAU,QAAQ,SAAS,SAAS,SACtC,MAAM,mBAAmB,QAAQ;AAAA,MACjC;AAAA,MACA,MAAM,QAAQ;AAAA,MACd,UAAU,QAAQ;AAAA,MAClB,WAAW,QAAQ;AAAA,IACrB,CAAC,IACC,MAAM,kBAAkB,QAAQ,EAAE,WAAW,MAAM,QAAQ,MAAM,WAAW,QAAQ,iBAAiB,CAAC;AAC1G,WAAO,QAAQ,SAAS,aAAa,gBAAgB,QAAQ,OAAO,IAAI;AAAA,EAC1E;AAEA,QAAM,QAAQ,YAA2B;AACvC,UAAM,SAAS,CAAC,GAAG,QAAQ,QAAQ,CAAC;AACpC,YAAQ,MAAM;AACd,eAAW,CAAC,KAAK,OAAO,KAAK,QAAQ;AACnC,YAAM,SAAS,MAAM;AACrB,UAAI,CAAC,OAAQ;AAGb,UAAI,IAAK,OAAM,kBAAkB,MAAM;AAAA,UAClC,QAAO,WAAW;AAAA,IACzB;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,UAAU,MAAM,MAAM;AACvC;;;AE3FA,IAAM,WAAW,CAAC,SAChB,OAAQ,KAAK,OAAO,KAAK,OAAQ;AAEnC,IAAM,eAAe,CAAC,KAAc,YAAsD;AAGxF,QAAM,IAAI;AAsBV,QAAM,OAAO,EAAE,YAAY,EAAE;AAE7B,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAA,IACL,KAAK,UAAU;AAMb,YAAM,gBAAgB,QAAQ,EAAE,MAAM,KACjC,EAAE,UAAU,mBACX,MAAM,QAAQ,EAAE,MAAM,KAAK,EAAE,OAAO,SAAS;AACnD,UAAI,SAAS,gBAAgB,CAAC,cAAe,QAAO,GAAG,QAAQ,YAAY;AAC3E,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAA,IACL,KAAK,UAAU;AACb,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAA,IACL,KAAK,WAAW;AACd,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAA,IACL,KAAK,QAAQ;AACX,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAA,IACL,KAAK,aAAa;AAChB,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,WAAW;AACd,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAA,IACL,KAAK,WAAW;AAEd,aAAO,EAAE,UAAU,MAAM,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO,CAAC,IAAI;AAAA,IAC7D;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,YAAY;AAEf,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,cAAc;AAMjB,UAAI,MAAM,QAAQ,EAAE,MAAM,EAAG,QAAO,EAAE,OAAO,CAAC;AAC9C,YAAM,UAAU,EAAE,YAAY,EAAE,UAAU,CAAC,MAAM,QAAQ,EAAE,MAAM,IAAI,EAAE,SAAS;AAChF,UAAI,SAAS;AACX,cAAM,QAAQ,OAAO,OAAO,OAAO,EAAE,CAAC;AACtC,YAAI,UAAU,OAAW,QAAO;AAAA,MAClC;AACA,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,SAAS;AAGZ,aAAO,aAAa,SAAS,EAAE,SAAS,GAAG,OAAO;AAAA,IACpD;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,YAAY;AAIf,aAAO,aAAa,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,GAAG,OAAO;AAAA,IACxE;AAAA,IACA,KAAK;AAAA,IACL,KAAK,SAAS;AACZ,YAAM,QAAQ,EAAE,SAAS,CAAC;AAC1B,aAAO,MAAM,IAAI,UAAQ,aAAa,SAAS,IAAI,GAAG,OAAO,CAAC;AAAA,IAChE;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,sBAAsB;AACzB,aAAO,EAAE,WAAW,EAAE,QAAQ,SAAS,KAAK,EAAE,QAAQ,CAAC,IAAI,aAAa,SAAS,EAAE,QAAQ,CAAC,CAAC,GAAG,OAAO,IAAI;AAAA,IAC7G;AAAA,IACA,KAAK;AAAA,IACL,KAAK,SAAS;AACZ,aAAO,CAAC;AAAA,IACV;AAAA,IACA,KAAK;AAAA,IACL,KAAK,UAAU;AACb,YAAM,MAA+B,CAAC;AAEtC,YAAM,QAAQ,OAAO,EAAE,UAAU,aAAa,EAAE,MAAM,IAAK,EAAE,SAAS,CAAC;AACvE,iBAAW,CAAC,KAAK,WAAW,KAAK,OAAO,QAAQ,KAAK,GAAG;AACtD,cAAM,SAAS,aAAa,SAAS,WAAW,GAAG,OAAO;AAC1D,YAAI,WAAW,OAAW,KAAI,GAAG,IAAI;AAAA,MACvC;AACA,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAA,IACL,KAAK,UAAU;AACb,aAAO,CAAC;AAAA,IACV;AAAA,IACA,KAAK;AAAA,IACL,KAAK,QAAQ;AACX,cAAO,oBAAI,KAAK,GAAE,YAAY;AAAA,IAChC;AAAA,IACA,SAAS;AAOP,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEO,IAAM,oBAAoB,CAAC,QAAmB,YAA2C;AAI9F,QAAM,OAAO;AACb,SAAO,aAAa,KAAK,OAAO,KAAK,MAAM,OAAO;AACpD;;;AC3KO,IAAM,uBAAuB,CAAC,YACnC,CAAC,aAA0C;AACzC,QAAM,UAAU,SAAS,SAAS,SAAS,QAAQ,mBAAmB,QAAQ;AAC9E,QAAM,SAAS,UAAU,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI,SAAS,OAAO;AAC3E,SAAO,SAAS,kBAAkB,QAAQ,EAAE,cAAc,QAAQ,aAAa,CAAC,IAAI,CAAC;AACvF;;;AC3BF,OAAO,QAAQ;AACf,OAAO,UAAU;AAEjB,IAAM,mBAAmB,CAAC,QAA0B;AAClD,MAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,UAAU,GAAG,KAAK,OAAO,EAAG,QAAO;AAC1E,MAAI;AACF,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;AAAA,EACT,SAAS,OAAO;AACd,WAAO,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS;AAAA,EACrE;AACF;AAYO,IAAM,qBAAqB,CAChC,UAAqC,CAAC,MAC3B;AACX,QAAM,WAAW,QAAQ,IAAI;AAC7B,MAAI,SAAU,QAAO;AAErB,MAAI;AACF,UAAM,MAAM,GAAG;AAAA,MACb,KAAK,KAAK,QAAQ,OAAO,QAAQ,IAAI,GAAG,gBAAgB,eAAe,iBAAiB;AAAA,MACxF;AAAA,IACF;AACA,UAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,QACE,OAAO,KAAK,SAAS,YAClB,OAAO,UAAU,KAAK,IAAI,KAC1B,KAAK,OAAO,KACZ,KAAK,QAAQ,SACb,iBAAiB,KAAK,GAAG,GAC5B;AACA,aAAO,oBAAoB,OAAO,KAAK,IAAI,CAAC;AAAA,IAC9C;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO,QAAQ,eAAe;AAChC;;;AC3CA,SAAS,cAAc,YAAAC,iBAAgB;AAwBhC,IAAM,4BAGuC,CAAC,OAAO,WAAW;AACrE,MACE,OAAO,UAAU,YACd,UAAU,QACV,EAAE,uBAAuB,UACzB,OAAO,MAAM,sBAAsB,YACtC;AACA,UAAM,IAAI,UAAU,iBAAiB,MAAM,6CAA6C;AAAA,EAC1F;AACF;AAEA,IAAM,WAAW,CAAC,UAChB,OAAO,UAAU,YAAY,UAAU;AAEzC,IAAM,UAAU,CAAC,UACf,OAAO,UAAU,YACb,SAAS,KAAK,KAAK,OAAO,MAAM,aAAa;AAEnD,IAAM,0BAA0B,CAAC,kBAA2D;AAC1F,MAAI,CAAC,SAAS,aAAa,EAAG,QAAO;AACrC,QAAM,YAAY,cAAc;AAChC,MAAI,CAAC,SAAS,SAAS,EAAG,QAAO;AAEjC,QAAM,MAAM,UAAU;AACtB,MAAI,QAAQ,UAAa,QAAQ,GAAI,QAAO;AAC5C,MAAI,OAAO,QAAQ,UAAU;AAC3B,UAAM,IAAI,UAAU,0DAA0D;AAAA,EAChF;AACA,MAAI,CAAC,QAAQ,UAAU,KAAK,GAAG;AAC7B,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,GAAG,WAAW,KAAK,OAAO,UAAU,MAAM;AACrD;AAOO,IAAM,yBAAyB,OACpC,QAAqC,CAAC,MACpB;AAClB,eAAa;AACb,MAAI,CAAC,MAAM,kBAAmB;AAE9B,QAAM,gBAAgB,MAAM,MAAM,kBAAkB;AACpD,QAAM,sBAAsB,wBAAwB,aAAa;AACjE,MAAI,CAAC,oBAAqB;AAE1B,QAAM,CAAC,aAAa,aAAa,IAAI,MAAMA;AAAA,IACzC,MAAM,OAAO,4BAA4B;AAAA,EAC3C;AACA,MAAI,eAAe,CAAC,eAAe;AACjC,UAAM,IAAI;AAAA,MACR;AAAA,MAGA,EAAE,OAAO,eAAe,OAAU;AAAA,IACpC;AAAA,EACF;AAEA,QAAM,cAAc,kBAAkB;AAAA,IACpC,GAAG;AAAA;AAAA;AAAA,IAGH,QAAQ;AAAA,EACV,CAAC;AACH;;;ACvGA,SAAS,kBAAkB;AAWpB,IAAM,mBAAmB;AAKzB,IAAM,uBAAuB,MAAc,GAAG,gBAAgB,GAAG,WAAW,EAAE,MAAM,GAAG,CAAC,CAAC;;;ACdhG,IAAM,aAAa,CAAC,WAAkD;AACpE,UAAQ,QAAQ;AAAA,IACd,KAAK,QAAQ;AAAE,aAAO;AAAA,IACtB;AAAA,IACA,KAAK,QAAQ;AAAE,aAAO;AAAA,IACtB;AAAA,IACA,KAAK,WAAW;AAAE,aAAO;AAAA,IACzB;AAAA,EACF;AACF;AAEO,IAAM,oBAAoB,CAAC,WAAsC;AACtE,QAAM,EAAE,UAAU,YAAY,YAAY,gBAAgB,WAAW,OAAO,IAAI;AAChF,QAAM,OAAO,WAAW,OAAO,MAAM;AACrC,QAAM,OAAO,GAAG,IAAI,IAAI,SAAS,MAAM,KAAK,SAAS,QAAQ,IAAI,UAAU;AAE3E,MAAI,OAAO,WAAW,QAAQ;AAC5B,UAAM,OAAO,mBAAmB,UAAU,YAAY,aAAa,SAAS,MAAM;AAClF,YAAQ,IAAI,GAAG,IAAI,SAAS,cAAc,GAAG,GAAG,IAAI,EAAE;AACtD;AAAA,EACF;AAEA,MAAI,OAAO,WAAW,WAAW;AAC/B,YAAQ,IAAI,GAAG,IAAI,IAAI,UAAU,EAAE,EAAE;AACrC;AAAA,EACF;AAEA,UAAQ,IAAI,GAAG,IAAI,SAAS,cAAc,GAAG,WAAW,UAAU,QAAQ,EAAE;AAC9E;AAEO,IAAM,qBAAqB,CAAC,YAAsC;AACvE,QAAM,EAAE,OAAO,QAAQ,QAAQ,QAAQ,IAAI;AAC3C,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,mBAAmB,MAAM,IAAI,KAAK,YAAY,MAAM,YAAY,OAAO,UAAU;AAE7F,MAAI,SAAS,GAAG;AACd,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,WAAW;AACvB,eAAW,UAAU,QAAQ,SAAS;AACpC,UAAI,OAAO,WAAW,OAAQ;AAC9B,cAAQ,IAAI,QAAQ,OAAO,SAAS,QAAQ,KAAK,OAAO,UAAU,aAAa,EAAE;AAAA,IACnF;AAAA,EACF;AACF;;;ACxCA,OAAO,YAAY;AACnB,OAAOC,SAAQ;AACf,OAAOC,WAAU;AACjB,SAAS,qBAAqB;AAE9B;AAAA,EACE,qBAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA,oBAAAC;AAAA,EACA;AAAA,EACA,YAAAC;AAAA,EACA;AAAA,OACK;;;ACFP,SAAS,oBAAAC,mBAAkB,YAAAC,iBAAgB;AA2C3C,IAAM,qBAAqB;AAC3B,IAAMC,SAAQ;AAEd,IAAM,qBAAqB,CAAC,UAC1B,OAAO,UAAU,YAAY,UAAU;AAElC,IAAM,oBAAoB,OAC/B,UACmC;AACnC,QAAM,mBAAmB,MAAM,oBAAoB;AACnD,QAAM,SAAmB,CAAC;AAC1B,QAAM,UAA+E,CAAC;AAItF,QAAM,SAAS,MAAM,kBAAkB,EAAE,SAAS,MAAM,SAAS,OAAO,MAAM,OAAO,OAAOA,OAAM,CAAC;AAEnG,QAAM,cAAc,CAAC,QAAuB;AAC1C,QAAI,CAAC,mBAAmB,GAAG,EAAG;AAC9B,QAAI,IAAI,WAAW,SAAU;AAC7B,QAAI,IAAI,aAAa,MAAM,cAAe;AAK1C,UAAM,QAAQ;AACd,WAAO,KAAK,KAAK;AAGjB,aAAS,IAAI,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;AAC5C,YAAM,SAAS,QAAQ,CAAC;AACxB,UAAI,CAAC,OAAQ;AACb,UAAI,OAAO,UAAU,MAAM,GAAG;AAC5B,gBAAQ,OAAO,GAAG,CAAC;AACnB,eAAO,QAAQ;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAEA,SAAO,GAAGC,kBAAiB,MAAM,WAAW;AAI5C,QAAM,CAAC,SAAS,IAAI,MAAMC,UAAS,MAAM,aAAa,QAAQ,MAAM,UAAU,EAAE,OAAOF,OAAM,CAAC,CAAC;AAC/F,MAAI,WAAW;AACb,WAAO,IAAIC,kBAAiB,MAAM,WAAW;AAC7C,WAAO,WAAW;AAClB,UAAM;AAAA,EACR;AAEA,MAAI,SAAS;AACb,QAAM,QAAQ,MAAqB;AACjC,QAAI,OAAQ,QAAO,QAAQ,QAAQ;AACnC,aAAS;AACT,WAAO,IAAIA,kBAAiB,MAAM,WAAW;AAE7C,eAAW,UAAU,QAAS,QAAO,QAAQ;AAC7C,YAAQ,SAAS;AACjB,WAAO,WAAW;AAClB,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAEA,QAAM,SAAS,CACb,WACA,YAAoB,qBACF;AAClB,QAAI,UAAU,MAAM,EAAG,QAAO,QAAQ,QAAQ;AAC9C,WAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,UAAI,UAAU;AACd,YAAM,SAAS;AAAA,QACb;AAAA,QACA,SAAS,MAAM;AACb,cAAI,QAAS;AACb,oBAAU;AACV,uBAAa,KAAK;AAClB,kBAAQ;AAAA,QACV;AAAA,MACF;AACA,YAAM,QAAQ,WAAW,MAAM;AAC7B,YAAI,QAAS;AACb,kBAAU;AACV,cAAM,MAAM,QAAQ,QAAQ,MAAM;AAClC,YAAI,QAAQ,GAAI,SAAQ,OAAO,KAAK,CAAC;AACrC,eAAO,IAAI;AAAA,UACT,sDAAsD,SAAS,gBAAgB,OAAO,MAAM;AAAA,QAC9F,CAAC;AAAA,MACH,GAAG,SAAS;AACZ,cAAQ,KAAK,MAAM;AAAA,IACrB,CAAC;AAAA,EACH;AAEA,QAAM,eAAe,CAAC,GAAW,YAAoB,qBAAoC;AACvF,WAAO,OAAO,CAAC,YAAY,QAAQ,UAAU,GAAG,SAAS;AAAA,EAC3D;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AD1HA,IAAM,wBAAwB;AAC9B,IAAM,gCAAgC;AAKtC,IAAM,0BAA0B;AA2EzB,IAAM,0BAA0B,CAAC,SAAiB,UAAU,MAA4B;AAC7F,MAAI,CAACE,IAAG,WAAW,MAAM,EAAG,QAAO,CAAC;AACpC,QAAM,QAA8B,CAAC;AACrC,QAAM,QAAkB,CAAC,MAAM;AAG/B,QAAM,UAAU,oBAAI,IAAY;AAChC,SAAO,MAAM,SAAS,GAAG;AACvB,UAAM,UAAU,MAAM,IAAI;AAC1B,QAAI,YAAY,OAAW;AAE3B,UAAM,CAAC,WAAW,QAAQ,IAAI,aAAa,MAAMA,IAAG,aAAa,OAAO,CAAC;AACzE,UAAM,YAAY,aAAa,aAAa,OAAO,UAAU;AAC7D,QAAI,QAAQ,IAAI,SAAS,EAAG;AAC5B,YAAQ,IAAI,SAAS;AAIrB,UAAM,CAAC,WAAW,OAAO,IAAI,aAAa,MAAMA,IAAG,YAAY,SAAS,EAAE,eAAe,KAAK,CAAC,CAAC;AAChG,QAAI,aAAa,YAAY,KAAM;AACnC,eAAW,SAAS,SAAS;AAC3B,YAAM,WAAWC,MAAK,KAAK,SAAS,MAAM,IAAI;AAG9C,UAAI,MAAM,eAAe,EAAG;AAC5B,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,KAAK,QAAQ;AACnB;AAAA,MACF;AACA,UAAI,CAAC,MAAM,OAAO,KAAK,CAAC,MAAM,KAAK,SAAS,WAAW,EAAG;AAC1D,YAAM,YAAYA,MAAK,SAAS,OAAO;AACvC,UAAI,cAAc,UAAU,cAAc,QAAS;AACnD,YAAM,UAAU,cAAc,SAAS,wBAAwB;AAC/D,YAAM,eAAe,QAAQ,KAAK,MAAM,IAAI;AAC5C,UAAI,CAAC,aAAc;AACnB,YAAM,UAAU,IAAI,aAAa,CAAC,CAAC;AACnC,YAAM,WAAW,MAAM,KAAK,QAAQ,cAAc,SAAS,sBAAsB,4BAA4B,EAAE;AAC/G,YAAM,UAAUA,MAAK,QAAQ,OAAO;AAIpC,YAAM,eAAeA,MAAK,SAAS,QAAQ,OAAO,EAAE,WAAW,MAAM,GAAG,KAAK;AAC7E,YAAM,KAAK;AAAA,QACT,UAAU;AAAA,QACV,MAAM,cAAc,SAAS,QAAQ;AAAA,QACrC,OAAO,EAAE,MAAM,cAAc,MAAM,UAAU,QAAQ;AAAA,MACvD,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO,MAAM,SAAS,CAAC,GAAG,MAAM,EAAE,SAAS,cAAc,EAAE,QAAQ,CAAC;AACtE;AA4EA,IAAM,iBAAN,cAA6B,MAAM;AAAA,EACjC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEA,IAAM,cAAc,OAAmB;AAAA,EACrC,IAAI,CAAC,QAAQ,UAAU,YAAY;AACjC,UAAM,KAAK,OAAO,GAAG,QAAQ,QAAQ,KAC/B,OAAO,WAAW,YAAY,OAAO,aAAa,YAAY,KAAK,UAAU,MAAM,MAAM,KAAK,UAAU,QAAQ;AACtH,QAAI,CAAC,IAAI;AACP,YAAM,SAAS,UAAU,GAAG,OAAO,OAAO;AAC1C,YAAM,IAAI,eAAe,GAAG,MAAM,YAAY,KAAK,UAAU,QAAQ,CAAC,YAAY,KAAK,UAAU,MAAM,CAAC,EAAE;AAAA,IAC5G;AAAA,EACF;AAAA,EACA,IAAI,CAAC,OAAO,YAAY;AACtB,QAAI,CAAC,MAAO,OAAM,IAAI,eAAe,WAAW,8BAA8B,KAAK,UAAU,KAAK,CAAC,EAAE;AAAA,EACvG;AAAA,EACA,QAAQ,OAAO,IAAI,YAAY;AAC7B,UAAM,CAAC,GAAG,IAAI,MAAMC,UAAS,EAAE;AAC/B,QAAI,CAAC,IAAK,OAAM,IAAI,eAAe,WAAW,sCAAsC;AACpF,WAAO;AAAA,EACT;AAAA,EACA,SAAS,CAAC,OAAO,SAAS,YAAY;AACpC,QAAI,CAAC,QAAQ,KAAK,KAAK,GAAG;AACxB,YAAM,SAAS,UAAU,GAAG,OAAO,OAAO;AAC1C,YAAM,IAAI,eAAe,GAAG,MAAM,IAAI,KAAK,oBAAoB,OAAO,EAAE;AAAA,IAC1E;AAAA,EACF;AACF;AAiBA,IAAM,mBAAmB,CAAC,aAA0C;AAClE,MAAI,CAAC,YAAY,OAAO,aAAa,SAAU,QAAO;AACtD,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,cAAc,YAAY,EAAE,UAAU,SAAS,EAAG,QAAO,EAAE;AACxE,MAAI,OAAO,EAAE,WAAW,YAAY,EAAE,WAAW,UAAW,QAAO,EAAE;AACrE,SAAO;AACT;AAQA,IAAM,sBAAsB,CAAC,UAAoD;AAC/E,SAAO;AAAA,IACL,OAAO,OAAO,SAAS;AACrB,YAAM,EAAE,aAAa,WAAW,IAAI,MAAM,OAAO,mBAAmB;AACpE,YAAM,SAAS,MAAM,MAAM,aAAa,KAAK,IAAI,CAAC,IAAI,OAAO,YAAY,CAAC,EAAE,SAAS,KAAK,CAAC;AAC3F,YAAM,QAAQ,MAAM,SAAS,QAAQ,KAAK,IAAI,CAAC,IAAI,OAAO,YAAY,CAAC,EAAE,SAAS,KAAK,CAAC;AACxF,YAAM,OAAO,MAAM,QAAQ;AAC3B,YAAM,QAAQ,QAAQ,KAAK,IAAI,CAAC,IAAI,OAAO,YAAY,EAAE,EAAE,SAAS,KAAK,CAAC;AAC1E,YAAM,cAAc;AAAA,QAClB,IAAI;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU;AAAA,MACZ;AACA,YAAM,YAAY,OAAO,aAAa,IAAI;AAC1C,YAAM,QAAQ;AACd,YAAM,SAAS;AAGf,YAAM,YAAY,MAAM,WAAW,KAAK;AACxC,YAAM,YAAY,WAAW,aAAa;AAC1C,aAAO,EAAE,OAAO,OAAO;AAAA,IACzB;AAAA,IACA,QAAQ,YAAY;AAClB,UAAI,CAAC,MAAM,MAAO;AAClB,YAAM,EAAE,cAAc,IAAI,MAAM,OAAO,mBAAmB;AAC1D,YAAM,cAAc,MAAM,KAAK;AAC/B,YAAM,QAAQ;AACd,YAAM,SAAS;AACf,YAAM,YAAY;AAAA,IACpB;AAAA,IACA,SAAS,OAAO,EAAE,OAAO,MAAM,OAAO,QAAQ,MAAM,OAAO;AAAA,EAC7D;AACF;AAOA,IAAM,gBAAgB,OAAO,UAAoB,UAA8C;AAC7F,QAAM,CAAC,WAAW,IAAI,IAAI,MAAMA,UAAS,MAAM,SAAS,KAAK,CAAC;AAC9D,MAAI,aAAa,SAAS,MAAM;AAC9B,UAAM,eAAe;AACrB,UAAM,IAAI,MAAM,sCAAsC,SAAS,MAAM,MAAM,WAAW,WAAW,SAAS,EAAE;AAAA,EAC9G;AACA,QAAM,CAAC,YAAY,MAAM,IAAI,aAAa,MAAM,KAAK,MAAM,IAAI,CAAY;AAC3E,MAAI,YAAY;AACd,UAAM,UAAU,KAAK,SAAS,MAAM,GAAG,KAAK,MAAM,GAAG,GAAG,CAAC,WAAM;AAC/D,UAAM,eAAe;AACrB,UAAM,IAAI,MAAM,qCAAqC,SAAS,MAAM,MAAM,OAAO,EAAE;AAAA,EACrF;AACA,QAAM,eAAe;AACrB,SAAO;AACT;AAEA,IAAM,uBAAuB,CAAC,SAC5B,MAAM,cAAcC,kBAAiB,EAAE,UAAU,eAAe,gBAAgB,SAAS;AAGpF,IAAM,eAAe,CAC1B,SACA,WACA,OACA,YAIA,QACA,eAC2B;AAE3B,SAAO,OAA4C,OAAe,SAA4C;AAG5G,QAAI,qBAAqB,IAAI,MAAM,UAAU;AAC3C,YAAM,EAAE,OAAO,IAAI,MAAM,WAAW,IAAI,MAAM,KAAK;AACnD,YAAM,UAAU,MAAM,kBAAkB,QAAQ,EAAE,WAAW,MAAM,OAAO,WAAW,wBAAwB,CAAC;AAC9G,UAAI,QAAQ,SAAS,WAAW;AAC9B,cAAM,eAAe;AACrB,cAAM,IAAI,MAAM,WAAW,SAAS,sCAAsC,uBAAuB,IAAI;AAAA,MACvG;AACA,YAAM,eAAe,QAAQ;AAE7B,aAAO,QAAQ;AAAA,IACjB;AAEA,UAAMF,QAAO,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC,QAAQ,SAAS;AAC3D,UAAM,UAAkC,EAAE,gBAAgB,oBAAoB,QAAQ,IAAI,IAAI,OAAO,EAAE,OAAO;AAC9G,QAAI,MAAM,MAAO,SAAQ,SAAS,GAAG,UAAU,IAAI,MAAM,KAAK;AAC9D,QAAI,MAAM,UAAW,SAAQ,cAAc,EAAE,UAAU,IAAI,MAAM;AACjE,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,gBAAgB,WAAW,MAAM;AAAE,iBAAW,MAAM;AAAA,IAAG,GAAG,uBAAuB;AACvF,UAAM,OAAoB,EAAE,QAAQ,SAAS,QAAQ,WAAW,OAAO;AAIvE,UAAM,WAAW,WAAW,SAAS,WAAW;AAChD,UAAM,MAAM,WAAWG,mBAAkBH,OAAM,SAAS,CAAC,CAAC,IAAIA;AAC9D,QAAI,CAAC,SAAU,MAAK,OAAO,KAAK,UAAU,SAAS,CAAC,CAAC;AACrD,UAAM,CAAC,YAAY,QAAQ,IAAI,MAAMC,UAAS,MAAM,MAAM,KAAK,IAAI,CAAC;AACpE,iBAAa,aAAa;AAC1B,QAAI,cAAc,CAAC,UAAU;AAC3B,YAAM,eAAe;AACrB,YAAM,IAAI,MAAM,WAAW,MAAM,IAAI,SAAS,YAAY,YAAY,WAAW,aAAa,EAAE;AAAA,IAClG;AAIA,WAAO,MAAM,cAAc,UAAU,KAAK;AAAA,EAC5C;AACF;AAYA,IAAM,uBAAuB,CAAC,SAAiB,eAAuC;AACpF,MAAI,UAAqF;AACzF,QAAM,QAAQ,MAAY;AACxB,aAAS,OAAO,WAAW;AAC3B,cAAU;AAAA,EACZ;AACA,SAAO;AAAA,IACL,KAAK,OAAO,UAAU;AACpB,UAAI,SAAS,UAAU,SAAS,QAAQ,OAAO,UAAW,QAAO;AACjE,YAAM;AACN,YAAM,SAAS,MAAM,kBAAkB,EAAE,SAAS,OAAO,OAAO,eAAe,mBAAmB,WAAW,CAAC;AAC9G,gBAAU,EAAE,QAAQ,OAAO,aAAa,oBAAI,IAAI,EAAE;AAClD,aAAO;AAAA,IACT;AAAA,IACA;AAAA,EACF;AACF;AAGA,IAAM,gBAAgB,CACpB,SACA,WACA,OACA,YACA,eAC4B;AAE5B,SAAO,OAA4C,OAAe,SAA6C;AAC7G,QAAI,qBAAqB,IAAI,MAAM,UAAU;AAC3C,YAAM,EAAE,QAAQ,YAAY,IAAI,MAAM,WAAW,IAAI,MAAM,KAAK;AAChE,YAAM,EAAE,UAAU,WAAW,KAAK,IAAI,QAAQ,CAAC;AAC/C,UAAI,YAAY,aAAa,SAAS,YAAY,CAAC,YAAY,IAAI,QAAQ,GAAG;AAG5E,cAAM,CAAC,SAAS,IAAI,MAAMA,UAAS,MAAM,aAAa,QAAQ,UAAU,EAAE,OAAO,WAAW,CAAC,CAAC;AAC9F,YAAI,CAAC,UAAW,aAAY,IAAI,QAAQ;AAAA,MAC1C;AACA,YAAM,UAAU,MAAM,mBAAmB,QAAQ;AAAA,QAC/C;AAAA,QACA,MAAM;AAAA,QACN;AAAA,QACA,YAAY,MAAM;AAAA,QAClB,WAAW;AAAA,MACb,CAAC;AACD,UAAI,QAAQ,SAAS,WAAW;AAC9B,cAAM,eAAe;AACrB,cAAM,IAAI,MAAM,YAAY,SAAS,sCAAsC,uBAAuB,IAAI;AAAA,MACxG;AACA,YAAM,eAAe,QAAQ;AAE7B,aAAO,QAAQ;AAAA,IACjB;AAEA,UAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC,SAAS,SAAS;AAC3D,UAAM,UAAkC,EAAE,gBAAgB,oBAAoB,QAAQ,IAAI,IAAI,OAAO,EAAE,OAAO;AAC9G,QAAI,MAAM,MAAO,SAAQ,SAAS,GAAG,UAAU,IAAI,MAAM,KAAK;AAC9D,QAAI,MAAM,UAAW,SAAQ,cAAc,EAAE,UAAU,IAAI,MAAM;AACjE,UAAM,OAAO,EAAE,MAAM,SAAS,CAAC,GAAG,UAAU,MAAM,UAAU,YAAY,MAAM,WAAW;AACzF,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,gBAAgB,WAAW,MAAM;AAAE,iBAAW,MAAM;AAAA,IAAG,GAAG,uBAAuB;AACvF,UAAM,CAAC,YAAY,QAAQ,IAAI,MAAMA,UAAS,MAAM,MAAM,KAAK;AAAA,MAC7D,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,MACzB,QAAQ,WAAW;AAAA,IACrB,CAAC,CAAC;AACF,iBAAa,aAAa;AAC1B,QAAI,cAAc,CAAC,UAAU;AAC3B,YAAM,eAAe;AACrB,YAAM,IAAI,MAAM,YAAY,SAAS,YAAY,YAAY,WAAW,aAAa,EAAE;AAAA,IACzF;AAEA,WAAO,MAAM,cAAc,UAAU,KAAK;AAAA,EAC5C;AACF;AAUA,IAAM,eAAe,CACnB,WACA,UACiB;AAIjB,QAAM,aAAa,MAAM,qBAAqB,yBAAyB;AACvE,QAAM,YAAY,GAAG,UAAU,MAAM,IAAI,IAAI,UAAU,MAAM,IAAI,IAAI,UAAU,MAAM,OAAO;AAC5F,QAAM,gBAAgB,QAAQ,SAAS;AAIvC,QAAM,YAAY,MAAM,eAAe,UAAU,MAAM,IAAI,IAAI,UAAU,MAAM,IAAI,IAAI,UAAU,MAAM,OAAO,KAAK;AACnH,QAAM,QAA0B,EAAE,OAAO,MAAM,QAAQ,MAAM,WAAW,MAAM,cAAc,KAAK;AAIjG,QAAM,WAA4B,CAAC;AACnC,QAAM,cAAc,OAClB,aACmC;AACnC,UAAM,UAAU,MAAM,kBAA0B;AAAA,MAC9C,SAAS,MAAM;AAAA,MACf;AAAA,MACA,OAAO,MAAM;AAAA,MACb;AAAA,IACF,CAAC;AAGD,aAAS,KAAK,OAAO;AACrB,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,qBAAqB,MAAM,SAAS,UAAU;AAEjE,QAAM,mBAAmB,YAA2B;AAClD,eAAW,MAAM;AACjB,WAAO,SAAS,SAAS,GAAG;AAC1B,YAAM,UAAU,SAAS,IAAI;AAC7B,UAAI,CAAC,QAAS;AACd,YAAM,CAAC,UAAU,IAAI,MAAMA,UAAS,MAAM,QAAQ,MAAM,CAAC;AACzD,UAAI,YAAY;AAAA,MAIhB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAmB;AAAA,IACvB,SAAS,aAAa,MAAM,SAAS,WAAW,OAAO,YAAY,WAAW,UAAU;AAAA,IACxF,UAAU,cAAc,MAAM,SAAS,WAAW,OAAO,YAAY,UAAU;AAAA,IAC/E;AAAA,IACA,SAAS,oBAAoB,KAAK;AAAA;AAAA;AAAA;AAAA,IAIlC,IAAI,SAAS;AACX,aAAO,gBAAgB;AAAA,IACzB;AAAA,IACA,QAAQ,YAAY;AAAA,EACtB;AAEA,SAAO,EAAE,KAAK,kBAAkB,MAAM;AACxC;AAGO,IAAM,yCAAyC,OACpD,UACmC;AACnC,QAAM,SAAS,UAAU;AACzB,QAAM,QAAQ,wBAAwB,MAAM;AAC5C,QAAM,UAA8B,CAAC;AAErC,aAAW,aAAa,OAAO;AAC7B,UAAM,YAAY,GAAG,UAAU,MAAM,IAAI,IAAI,UAAU,MAAM,IAAI,IAAI,UAAU,MAAM,OAAO;AAC5F,QAAI,MAAM,UAAU,CAAC,UAAU,SAAS,MAAM,MAAM,EAAG;AAKvD,UAAM,CAAC,aAAa,YAAY,IAAI,aAAa,MAAMF,IAAG,aAAa,UAAU,QAAQ,CAAC;AAC1F,UAAM,eAAe,cAAc,UAAU,WAAY,gBAAgB,UAAU;AACnF,UAAM,WAAWC,MAAK,SAAS,QAAQ,YAAY;AACnD,QAAI,SAAS,WAAW,IAAI,KAAKA,MAAK,WAAW,QAAQ,GAAG;AAC1D,YAAM,IAAsB;AAAA,QAC1B;AAAA,QAAW,UAAU;AAAA,QAAY,QAAQ;AAAA,QAAQ,YAAY;AAAA,QAC7D,QAAQ,mDAAmD,UAAU,QAAQ;AAAA,MAC/E;AACA,cAAQ,KAAK,CAAC;AACd,YAAM,WAAW,CAAC;AAClB;AAAA,IACF;AAEA,UAAM,CAAC,aAAa,GAAG,IAAI,MAAMC;AAAA,MAC/B,MAAM,OAAO,cAAc,YAAY,EAAE;AAAA,IAC3C;AACA,QAAI,eAAe,CAAC,KAAK;AACvB,YAAM,IAAsB;AAAA,QAC1B;AAAA,QAAW,UAAU;AAAA,QAAY,QAAQ;AAAA,QAAQ,YAAY;AAAA,QAC7D,QAAQ,oBAAoB,UAAU,QAAQ,KAAK,aAAa,WAAW,SAAS;AAAA,MACtF;AACA,cAAQ,KAAK,CAAC;AACd,YAAM,WAAW,CAAC;AAClB;AAAA,IACF;AAEA,UAAM,WAAY,IAAkC;AASpD,QAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,GAAG;AACrD;AAAA,IACF;AAEA,eAAW,WAAW,UAAU;AAC9B,YAAM,IAAI;AACV,UAAI,OAAO,EAAE,SAAS,YAAY,OAAO,EAAE,QAAQ,WAAY;AAC/D,YAAM,iBAAiB,OAAO,EAAE,mBAAmB,WAAW,EAAE,iBAAiB;AACjF,YAAM,QAAQ,aAAa,WAAW,KAAK;AAC3C,YAAM,UAAU,KAAK,IAAI;AACzB,YAAM,CAAC,QAAQ,IAAI,MAAMA,UAAS,YAAY,EAAE,IAAI,MAAM,GAAG,CAAC;AAI9D,YAAMA,UAAS,MAAM,MAAM,iBAAiB,CAAC;AAK7C,YAAM,gBAAgB,MAAM,MAAM;AAClC,UAAI,eAAe;AACjB,cAAMA,UAAS,YAAY;AACzB,gBAAM,EAAE,cAAc,IAAI,MAAM,OAAO,mBAAmB;AAC1D,gBAAM,cAAc,aAAa;AAAA,QACnC,CAAC;AAAA,MACH;AACA,YAAM,aAAa,KAAK,IAAI,IAAI;AAEhC,UAAI;AACJ,UAAI,UAAU;AAGZ,cAAM,YAAY,iBAAiB,MAAM,MAAM,YAAY;AAC3D,cAAM,SAAS,YAAY,GAAG,SAAS,OAAO,aAAa,SAAS,MAAM,SAAS;AACnF,YAAI,iBACA,EAAE,WAAW,UAAU,EAAE,MAAM,QAAQ,SAAS,YAAY,QAAQ,eAAe,IACnF,EAAE,WAAW,UAAU,EAAE,MAAM,QAAQ,QAAQ,YAAY,OAAO;AAAA,MACxE,OAAO;AACL,YAAI,iBACA;AAAA,UACE;AAAA,UAAW,UAAU,EAAE;AAAA,UAAM,QAAQ;AAAA,UAAS;AAAA,UAC9C,QAAQ,2BAA2B,cAAc;AAAA,UACjD;AAAA,QACF,IACA,EAAE,WAAW,UAAU,EAAE,MAAM,QAAQ,QAAQ,WAAW;AAAA,MAChE;AACA,cAAQ,KAAK,CAAC;AACd,YAAM,WAAW,CAAC;AAAA,IACpB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ,OAAO,OAAK,EAAE,WAAW,MAAM,EAAE;AAAA,IACjD,QAAQ,QAAQ,OAAO,OAAK,EAAE,WAAW,MAAM,EAAE;AAAA,IACjD,SAAS,QAAQ,OAAO,OAAK,EAAE,WAAW,OAAO,EAAE;AAAA,IACnD,SAAS,QAAQ,OAAO,OAAK,EAAE,WAAW,OAAO,EAAE;AAAA,IACnD;AAAA,EACF;AACF;AAMO,IAAM,iBAAiB,OAAO,UAA+D;AAClG,4BAA0B,OAAO,gBAAgB;AACjD,QAAM,uBAAuB,EAAE,mBAAmB,MAAM,kBAAkB,CAAC;AAC3E,SAAO,uCAAuC,KAAK;AACrD;;;AEtrBA,OAAOG,aAAY;AACnB,OAAOC,SAAQ;AACf,OAAOC,WAAU;AAGjB,SAAS,oBAAoB,iBAAAC,gBAAe,oBAAAC,mBAAkB,YAAAC,WAAU,gBAAAC,qBAAoB;AAkI5F,IAAM,2BAA2B,CAAC,UAA2C;AAC3E,MAAI,MAAM,kBAAkB,MAAO,QAAO;AAC1C,QAAM,QAAQ,MAAM,cAAc,QAAQ,IAAI;AAC9C,MAAI,CAAC,OAAO;AACV,YAAQ;AAAA,MACN;AAAA,IAGF;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAUA,IAAM,mBAAmB,OAAO,UAA6D;AAC3F,QAAM,UAAkC,CAAC;AACzC,MAAI,CAAC,MAAM,UAAW,QAAO;AAE7B,MAAIC,kBAAiB,EAAE,QAAQ,YAAY;AACzC,YAAQ,gBAAgB,UAAU,MAAM,SAAS;AACjD,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,MAAM,qBAAqB,yBAAyB;AACvE,UAAQ,SAAS,GAAG,UAAU,IAAI,MAAM,SAAS;AAKjD,MAAI;AACF,UAAM,EAAE,WAAW,IAAI,MAAM,OAAO,mBAAmB;AACvD,UAAM,UAAU,MAAM,WAAW,MAAM,SAAS;AAChD,QAAI,SAAS,WAAW;AACtB,cAAQC,eAAc,EAAE,UAAU,IAAI,QAAQ;AAAA,IAChD;AAAA,EACF,SAAS,OAAO;AAKd,YAAQ;AAAA,MACN;AAAA,MACA,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MACrD;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAcA,IAAM,mBAAmB,CAAC,aACxB,aAAaD,kBAAiB,EAAE,UAAU,eAAe,gBAAgB,SAAS;AAWpF,IAAM,yBAAyB,OAAO,OAAyB,kBAAqD;AAClH,QAAM,WAAW,eAAeE,QAAO,WAAW,CAAC;AACnD,QAAM,oBAAoB,MAAM,qBAAqB,yBAAyB;AAC9E,QAAM,WAAW,iBAAiB,MAAM,aAAa;AACrD,QAAM,UAAU,iBAAiB,MAAM,YAAY;AACnD,QAAM,OAAO,yBAAyB,EAAE,cAAc,SAAS,CAAC;AAChE,QAAM,SAAS,YAAY,YAAa,iBAAiB,aAAa,WAClE,2BAA2B,EAAE,SAAS,MAAM,SAAS,UAAU,kBAAkB,CAAC,IAClF;AAEJ,MAAI,eAA8B;AAClC,MAAI,iBAAiB,MAAM,WAAW;AACpC,UAAM,YAAY,MAAM;AACxB,UAAM,CAAC,cAAcC,OAAM,IAAI,MAAMC,UAAS,MAAM,kBAAkB;AAAA,MACpE,SAAS,MAAM;AAAA,MACf,OAAO;AAAA,MACP,OAAO;AAAA,MACP;AAAA,IACF,CAAC,CAAC;AACF,QAAI,gBAAgB,CAACD,SAAQ;AAC3B,cAAQ,KAAK,gFAA2E,cAAc,WAAW,WAAW,gDAAgD;AAAA,IAC9K,OAAO;AACL,qBAAeA;AACf,YAAM,CAAC,SAAS,IAAI,MAAMC,UAAS,MAAM,aAAaD,SAAQ,UAAU,EAAE,OAAO,aAAa,CAAC,CAAC;AAChG,UAAI,UAAW,SAAQ,KAAK,8BAA8B,UAAU,OAAO,gDAAgD;AAAA,IAC7H;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM,gBAAiB,aAAa,YAAY,SAAS,SAAS,OAAQ;AAAA,IAC1E,UAAU,gBAAgB,OAAO;AAAA,IACjC,KAAK,YAAY,YAAY,SAAS,SAAS;AAAA,IAC/C,OAAO,YAAY;AACjB,YAAM,QAAQ,QAAQ;AAGtB,UAAI,aAAc,OAAM,kBAAkB,YAAY;AAAA,IACxD;AAAA,EACF;AACF;AAMA,IAAM,iBAAiB,OACrB,OACA,SACA,SACA,aACkB;AAClB,QAAM,aAAa,MAAM,cAAc,QAAQ,IAAI;AAGnD,QAAM,oBAAoB,yBAAyB,KAAK;AACxD,MAAI,mBAAmB;AACrB,UAAM,iBAAiB,EAAE,SAAS,MAAM,SAAS,OAAO,mBAAmB,MAAM,CAAC,YAAY,EAAE,CAAC;AAAA,EACnG;AAEA,QAAM,gBAAgB,OAAO,KAAK,MAAM,eAAe,CAAC,CAAC,EAAE,SAAS;AACpE,QAAM,aAAa,MAAM,uBAAuB,OAAO,aAAa;AACpE,QAAM,UAAU;AAAA,IACd,cAAc,MAAM;AAAA,IACpB,aAAa,MAAM;AAAA,IACnB,SAAS,MAAM;AAAA,IACf,MAAM,MAAM;AAAA,IACZ,QAAQ,MAAM;AAAA,EAChB;AAEA,QAAM,SAAS,EAAE,GAAG,SAAS,eAAe,WAAW,MAAM,cAAc,WAAW,IAAI;AAG1F,QAAM,CAAC,UAAU,IAAI,MAAMC,UAAS,YAAY;AAC9C,YAAQ,WAAW,MAAM,iBAAiB;AAAA,MACxC,GAAG;AAAA,MACH;AAAA,MACA;AAAA,MACA,mBAAmB,MAAM;AAAA;AAAA;AAAA;AAAA,MAIzB;AAAA,IACF,CAAC;AAED,YAAQ,OAAO,MAAM,wBAAwB,EAAE,GAAG,QAAQ,YAAY,MAAM,YAAY,SAAS,CAAC;AAElG,QAAI,CAAC,MAAM,aAAa;AACtB,cAAQ,YAAY,MAAM,kBAAkB;AAAA,QAC1C,GAAG;AAAA,QACH,YAAY,MAAM;AAAA,QAClB;AAAA,QACA;AAAA;AAAA;AAAA;AAAA;AAAA,QAKA,uBAAuB;AAAA;AAAA;AAAA;AAAA,QAIvB;AAAA,MACF,CAAC;AAAA,IACH;AAUA,QAAI,CAAC,MAAM,UAAU,QAAQ,eAAe;AAC1C,cAAQ,IAAI,oIAA+H;AAAA,IAC7I,WAAW,CAAC,MAAM,UAAU,QAAQ,QAAQ;AAC1C,cAAQ,OAAO,MAAM,wBAAwB;AAAA,QAC3C,GAAG;AAAA,QACH,YAAY,MAAM;AAAA,QAClB,YAAY,QAAQ;AAAA,QACpB;AAAA;AAAA,QAEA,eAAe,WAAW;AAAA,MAC5B,CAAC;AAAA,IACH;AAEA,QAAI,CAAC,MAAM,QAAQ;AACjB,cAAQ,OAAO,MAAM,aAAa,EAAE,GAAG,QAAQ,SAAS,WAAW,CAAC;AAAA,IACtE;AAAA,EACF,CAAC;AAED,QAAM,WAAW,MAAM;AAIvB,MAAI,mBAAmB;AACrB,UAAM,iBAAiB,EAAE,SAAS,MAAM,SAAS,OAAO,mBAAmB,MAAM,CAAC,YAAY,EAAE,CAAC;AAAA,EACnG;AAEA,MAAI,WAAY,OAAM;AACxB;AAKA,IAAM,iBAAiB,OAAO,OAAyB,YAA+C;AAMpG,MAAI;AACF,UAAM,mBAAmB;AAAA,EAC3B,QAAQ;AAAA,EAER;AACA,QAAM,SAAS,MAAM,uCAAuC;AAAA,IAC1D,SAAS,MAAM;AAAA,IACf,mBAAmB,MAAM;AAAA,IACzB,QAAQ,MAAM;AAAA;AAAA;AAAA,IAGd,cAAc,MAAM;AAAA,EACtB,CAAC;AACD,UAAQ,SAAS;AACnB;AAKA,IAAM,oBAAoB;AAAA,EACxB,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AACb;AASA,IAAM,qBAAiI;AAAA,EACrI,CAAC,WAAW,QAAQ,GAAG;AAAA,EACvB,CAAC,WAAW,IAAI,GAAG;AAAA,EACnB,CAAC,WAAW,SAAS,GAAG;AAAA,EACxB,CAAC,WAAW,IAAI,GAAG;AAAA,EACnB,CAAC,WAAW,IAAI,GAAG;AACrB;AAYA,IAAM,gBAAgB,CAAC,YAAsC;AAC3D,MAAI,SAAS;AACb,MAAI,SAAS;AACb,aAAW,OAAO,mBAAmB;AACnC,UAAM,IAAI,QAAQ,mBAAmB,GAAG,CAAC;AACzC,cAAU,GAAG,UAAU;AACvB,cAAU,GAAG,UAAU;AAAA,EACzB;AACA,UAAQ,cAAc,UAAU,QAAQ,QAAQ,UAAU;AAC1D,UAAQ,cAAc,UAAU,QAAQ,QAAQ,UAAU;AAC5D;AAEO,IAAM,cAAc,OAAO,UAAyD;AACzF,4BAA0B,OAAO,aAAa;AAI9C,QAAM,uBAAuB,EAAE,mBAAmB,MAAM,kBAAkB,CAAC;AAE3E,QAAM,UAA8B,EAAE,aAAa,GAAG,aAAa,EAAE;AAOrE,MAAI,CAAC,MAAM,SAAS;AAClB,UAAM,YAAY,OAAO,KAAK,MAAM,YAAY,EAAE,SAAS;AAC3D,UAAM,UAAU,OAAO,KAAK,MAAM,UAAU,EAAE,SAAS;AACvD,QAAI,aAAa,CAAC,SAAS;AACzB,cAAQ;AAAA,QACN;AAAA,MAGF;AAAA,IACF;AAGA,QAAI,CAAC,MAAM,aAAa;AACtB,cAAQ;AAAA,QACN;AAAA,MAEF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,iBAAiB,KAAK;AAG5C,QAAM,WAAW,qBAAqB;AAAA,IACpC,iBAAiB,MAAM;AAAA,IACvB,kBAAkB,MAAM;AAAA,IACxB,cAAc,qBAAqB;AAAA,EACrC,CAAC;AAED,MAAI,CAAC,MAAM,SAAS;AAClB,UAAM,eAAe,OAAO,SAAS,SAAS,QAAQ;AAAA,EACxD;AAEA,MAAI,CAAC,MAAM,UAAU;AACnB,UAAM,eAAe,OAAO,OAAO;AAAA,EACrC;AAEA,gBAAc,OAAO;AACrB,mBAAiB,SAAS,MAAM,UAAU;AAE1C,SAAO;AACT;AAKA,IAAM,mBAAmB,CAAC,SAA6B,eAAiD;AACtG,MAAI,eAAe,MAAO;AAC1B,QAAM,SAASC,MAAK,QAAQ,cAAc,QAAQ,IAAI,oBAAoB,mBAAmB;AAC7F,QAAM,CAAC,UAAU,IAAIC,cAAa,MAAM;AACtC,IAAAC,IAAG,cAAc,QAAQ,KAAK,UAAU,SAAS,MAAM,CAAC,GAAG,MAAM;AAAA,EACnE,CAAC;AACD,MAAI,YAAY;AACd,YAAQ,KAAK,8CAA8C,MAAM,WAAM,WAAW,OAAO,EAAE;AAC3F;AAAA,EACF;AACA,UAAQ,IAAI,yCAAyC,MAAM,EAAE;AAC/D;AAKA,IAAM,YAAY,MAAe;AAC/B,MAAI,QAAQ,IAAI,SAAU,QAAO;AACjC,MAAI,QAAQ,IAAI,YAAa,QAAO;AACpC,SAAO,QAAQ,OAAO;AACxB,GAAG;AACH,IAAM,QAAQ,CAAC,MAAc,SAA0B,WAAW,QAAU,IAAI,IAAI,IAAI,YAAc;AACtG,IAAM,QAAQ,CAAC,MAAsB,MAAM,MAAM,CAAC;AAClD,IAAM,MAAM,CAAC,MAAsB,MAAM,MAAM,CAAC;AAChD,IAAM,SAAS,CAAC,MAAsB,MAAM,MAAM,CAAC;AACnD,IAAM,MAAM,CAAC,MAAsB,MAAM,KAAK,CAAC;AAC/C,IAAM,OAAO,CAAC,MAAsB,MAAM,KAAK,CAAC;AAiBhD,IAAM,UAAU,CAAC,MAAwC;AACvD,MAAI,MAAM,UAAa,EAAE,aAAa,GAAI,QAAO;AACjD,QAAM,IAAK,EAA8B;AACzC,SAAO,OAAO,MAAM,WAAW,IAAI;AACrC;AACA,IAAM,YAAY,CAAC,MAAwC;AACzD,MAAI,MAAM,UAAa,EAAE,aAAa,GAAI,QAAO;AACjD,QAAM,IAAK,EAA8B;AACzC,SAAO,OAAO,MAAM,WAAW,IAAI;AACrC;AAKA,IAAM,kBAAkB,CAAC,OAAe,MAAwC;AAC9E,MAAI,CAAC,EAAG,QAAO,KAAK,IAAI,QAAG,CAAC,IAAI,MAAM,OAAO,EAAE,CAAC,IAAI,IAAI,iBAAiB,CAAC;AAC1E,QAAM,QAAQ,EAAE,WAAW;AAC3B,QAAM,OAAO,QAAQ,MAAM,QAAG,IAAI,IAAI,QAAG;AACzC,QAAM,OAAO,QACT,MAAM,GAAG,EAAE,MAAM,IAAI,EAAE,KAAK,SAAS,IACrC,GAAG,MAAM,GAAG,EAAE,MAAM,IAAI,EAAE,KAAK,SAAS,CAAC,KAAK,IAAI,GAAG,EAAE,MAAM,IAAI,EAAE,KAAK,SAAS,CAAC;AACtF,QAAM,SAAmB,CAAC;AAC1B,QAAM,KAAK,QAAQ,CAAC;AACpB,QAAM,KAAK,UAAU,CAAC;AACtB,MAAI,GAAI,QAAO,KAAK,OAAO,GAAG,EAAE,gBAAgB,CAAC;AACjD,MAAI,GAAI,QAAO,KAAK,IAAI,GAAG,EAAE,UAAU,CAAC;AACxC,QAAM,QAAQ,OAAO,SAAS,IAAI,KAAK,IAAI,MAAG,CAAC,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK;AAC9E,SAAO,KAAK,IAAI,IAAI,MAAM,OAAO,EAAE,CAAC,IAAI,IAAI,GAAG,KAAK;AACtD;AAEA,IAAM,gBAAgB,CAAC,OACpB,GAAG,WAAW,CAAC,GACb,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EACjC,IAAI,CAAC,OAAO;AAAA,EACX,OAAO,GAAG,EAAE,SAAS,IAAI,IAAI,EAAE,SAAS,IAAI,IAAI,EAAE,SAAS,OAAO;AAAA,EAClE,QAAQ,EAAE,YAAY,GAAG,EAAE,UAAU,QAAQ,KAAK,EAAE,SAAS,MAAM,EAAE;AACvE,EAAE;AAEN,IAAM,gBAAgB,CAAC,OAAe,OACnC,GAAG,WAAW,CAAC,GACb,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,EACpC,IAAI,CAAC,OAAO;AAAA,EACX,OAAO,GAAG,KAAK,KAAK,EAAE,SAAS,IAAI,IAAI,EAAE,SAAS,IAAI,IAAI,EAAE,SAAS,OAAO;AAAA,EAC5E,QAAQ,EAAE;AACZ,EAAE;AAEN,IAAM,aAAa,CAAC,GAAsC,YACvD,GAAG,WAAW,CAAC,GACb,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EACjC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,EAAE,SAAS,OAAO,EAAE,QAAQ,IAAI,QAAQ,EAAE,OAAO,EAAE;AAEhF,IAAM,eAAe,CAAC,OAA8B,SAAiB,MAAmB,WAAyB;AAC/G,MAAI,KAAK,WAAW,EAAG;AACvB,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,MAAM,KAAK,GAAG,OAAO,KAAK,KAAK,MAAM,GAAG,CAAC,CAAC;AACtD,aAAW,OAAO,MAAM;AACtB,YAAQ,IAAI,KAAK,MAAM,MAAM,CAAC,IAAI,IAAI,KAAK,EAAE;AAC7C,QAAI,IAAI,OAAQ,SAAQ,IAAI,SAAS,IAAI,IAAI,MAAM,CAAC,EAAE;AAAA,EACxD;AACF;AAEO,IAAM,mBAAmB,CAAC,YAAsC;AACrE,UAAQ,IAAI,EAAE;AAGd,aAAW,OAAO,mBAAmB;AACnC,YAAQ,IAAI,gBAAgB,KAAK,QAAQ,mBAAmB,GAAG,CAAC,CAAC,CAAC;AAAA,EACpE;AACA,UAAQ,IAAI,gBAAgB,WAAW,QAAQ,QAAQ,MAAM,CAAC;AAI9D,QAAM,SAAsB;AAAA,IAC1B,GAAG,kBAAkB,QAAQ,CAAC,QAAQ,cAAc,QAAQ,mBAAmB,GAAG,CAAC,CAAC,CAAC;AAAA,IACrF,GAAG,WAAW,QAAQ,QAAQ,MAAM;AAAA,EACtC;AACA,eAAa,KAAK,yDAAoD,QAAQ,QAAG;AAGjF,eAAa,QAAQ,0DAAqD,WAAW,QAAQ,QAAQ,OAAO,GAAG,QAAG;AAGlH,eAAa,QAAQ,+DAA0D,WAAW,QAAQ,QAAQ,OAAO,GAAG,GAAG;AAGvH,QAAM,UAAuB,kBAAkB;AAAA,IAAQ,CAAC,QACtD,cAAc,KAAK,QAAQ,mBAAmB,GAAG,CAAC,CAAC;AAAA,EACrD;AACA,eAAa,KAAK,wCAAmC,SAAS,QAAG;AAGjE,MAAI,aAAa;AACjB,MAAI,eAAe;AACnB,aAAW,OAAO,mBAAmB;AACnC,UAAM,IAAI,QAAQ,mBAAmB,GAAG,CAAC;AACzC,kBAAc,QAAQ,CAAC;AACvB,oBAAgB,UAAU,CAAC;AAAA,EAC7B;AACA,gBAAc,QAAQ,QAAQ,MAAM;AACpC,kBAAgB,UAAU,QAAQ,MAAM;AAKxC,QAAM,aAAa,QAAQ,QAAQ,WAAW;AAE9C,UAAQ,IAAI,EAAE;AACd,QAAM,QAAQ;AAAA,IACZ,MAAM,GAAG,QAAQ,WAAW,SAAS;AAAA,IACrC,QAAQ,cAAc,IAAI,IAAI,GAAG,QAAQ,WAAW,SAAS,IAAI,IAAI,UAAU;AAAA,IAC/E,aAAa,IAAI,OAAO,GAAG,UAAU,gBAAgB,IAAI,IAAI,iBAAiB;AAAA,IAC9E,aAAa,IAAI,OAAO,GAAG,UAAU,sBAAsB,IAAI,IAAI,uBAAuB;AAAA,IAC1F,eAAe,IAAI,OAAO,GAAG,YAAY,UAAU,IAAI,IAAI,WAAW;AAAA,EACxE;AACA,UAAQ,IAAI,YAAY,MAAM,KAAK,IAAI,UAAO,CAAC,CAAC,EAAE;AAClD,UAAQ,IAAI,IAAI,kGAA6E,CAAC;AAChG;;;AClkBA,IAAM,SAAS,oBAAI,IAAuB;AAC1C,IAAM,WAAW,oBAAI,IAAyB;AAC9C,IAAI,WAAgC;AAO7B,IAAM,oBAAoB,CAAC,UAA2B;AAC3D,SAAO,IAAI,MAAM,MAAM,KAAK;AAC9B;AAEO,IAAM,iBAAiB,MAAmB,CAAC,GAAG,OAAO,OAAO,CAAC;AAU7D,IAAM,sBAAsB,CAAqB,SAAiB,YAAyC;AAChH,WAAS,IAAI,SAAS,OAAO;AAC/B;AAEO,IAAM,iBAAiB,CAAC,YAA6C,SAAS,IAAI,OAAO;AAQzF,IAAM,uBAAuB,CAAC,gBAA2C;AAC9E,aAAW;AACb;AAEO,IAAM,kBAAkB,MAA2B;AAEnD,IAAM,8BAA8B,MAAY;AACrD,SAAO,MAAM;AACb,WAAS,MAAM;AACf,aAAW;AACb;;;AC/GA,SAAS,YAAAC,WAAU,gBAAAC,qBAAoB;AAmCvC,IAAM,iBAAiB,CAAC,SACtB,SAAS,eACN,SAAS,SACT,SAAS,WACT,uBAAuB,KAAK,IAAI;AAIrC,IAAM,cAAc,OAAO,YAAwC;AACjE,QAAMC,YAAW,gBAAgB;AACjC,QAAM,aAAaA,WAAU;AAC7B,MAAI,CAACA,aAAY,CAAC,WAAY;AAK9B,QAAM,CAAC,EAAE,SAAS,IAAIC,cAAa,MAAM,IAAI,IAAI,UAAU,CAAC;AAC5D,MAAI,WAAW,aAAa,WAAW,CAAC,eAAe,UAAU,QAAQ,GAAG;AAC1E,YAAQ;AAAA,MACN,+EAA+E,UAAU,QAAQ;AAAA,IAEnG;AAAA,EACF;AACA,QAAM,UAAkC,EAAE,gBAAgB,mBAAmB;AAC7E,MAAID,UAAS,aAAa,SAAS,SAAU,SAAQ,gBAAgB,UAAUA,UAAS,YAAY,KAAK;AACzG,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,gBAAgB,WAAW,MAAM;AAAE,eAAW,MAAM;AAAA,EAAG,GAAG,GAAM;AACtE,QAAME,UAAS,MAAM,MAAM,YAAY;AAAA,IACrC,QAAQ;AAAA,IACR;AAAA,IACA,MAAM,KAAK,UAAU,OAAO;AAAA,IAC5B,QAAQ,WAAW;AAAA,EACrB,CAAC,CAAC;AACF,eAAa,aAAa;AAC5B;AAEO,IAAM,sBAAsB,OAAO,UAA0D;AAClG,QAAMC,UAAS,eAAe;AAC9B,QAAM,YAAY,gBAAgB,KAAK;AACvC,QAAMH,YAAW,gBAAgB;AACjC,QAAM,OAAO,MAAM,QAAQ,CAAC;AAC5B,QAAM,UAAwB,CAAC;AAG/B,QAAM,WAAW,MAAM,YAAY,qBAAqB;AAAA,IACtD,iBAAiB,MAAM;AAAA,IACvB,kBAAkB,MAAM;AAAA,IACxB,cAAc,qBAAqB;AAAA,EACrC,CAAC;AAED,aAAW,SAASG,SAAQ;AAC1B,eAAW,YAAY,WAAW;AAChC,UAAI,WAAW,UAAU,IAAI,EAAG;AAChC,YAAM,UAAU,KAAK,IAAI;AACzB,YAAM,CAAC,UAAU,WAAW,IAAI,MAAMD,UAAS,YAC7C,MAAM,IAAI;AAAA,QACR,UAAU,SAAS;AAAA,QACnB,QAAQ,SAAS,SAAS,SAAS,SAAY,SAAS;AAAA,QACxD,WAAW,MAAM;AAAA,QACjB,aAAa,SAAS,QAAQ;AAAA,MAChC,CAAC,CAAC;AACJ,YAAM,aAAa,KAAK,IAAI,IAAI;AAEhC,YAAM,SAAqB,YAAY,CAAC,cACpC;AAAA,QACE,OAAO,MAAM;AAAA,QACb,UAAU,SAAS;AAAA,QACnB,QAAQ;AAAA,QACR,SAAS,UAAU,WAAW;AAAA,QAC9B;AAAA,MACF,IACA;AAAA,QACE,OAAO,MAAM;AAAA,QACb,UAAU,SAAS;AAAA,QACnB,QAAQ,YAAY;AAAA,QACpB,SAAS,YAAY;AAAA,QACrB;AAAA,QACA,UAAU,YAAY;AAAA,MACxB;AAEJ,cAAQ,KAAK,MAAM;AACnB,YAAMA,UAAS,YAAYF,WAAU,WAAW,MAAM,CAAC;AAAA,IACzD;AAAA,EACF;AAEA,QAAM,UAAuB;AAAA,IAC3B,aAAaG,QAAO;AAAA,IACpB,gBAAgB,UAAU;AAAA,IAC1B,QAAQ,QAAQ,OAAO,OAAK,EAAE,MAAM,EAAE;AAAA,IACtC,QAAQ,QAAQ,OAAO,OAAK,CAAC,EAAE,MAAM,EAAE;AAAA,IACvC;AAAA,EACF;AAEA,QAAMD,UAAS,YAAYF,WAAU,YAAY,OAAO,CAAC;AACzD,QAAM,YAAY,OAAO;AAEzB,SAAO;AACT;","names":["tryCatch","DEFAULT_REQUEST_TIMEOUT_MS","DEFAULT_REQUEST_TIMEOUT_MS","DEFAULT_REQUEST_TIMEOUT_MS","DEFAULT_REQUEST_TIMEOUT_MS","DEFAULT_REQUEST_TIMEOUT_MS","tryCatch","getProjectConfig","tryCatch","tryCatch","tryCatch","fs","path","buildRoutedGetUrl","getProjectConfig","tryCatch","socketEventNames","tryCatch","LABEL","socketEventNames","tryCatch","fs","path","tryCatch","getProjectConfig","buildRoutedGetUrl","crypto","fs","path","getCsrfConfig","getProjectConfig","tryCatch","tryCatchSync","getProjectConfig","getCsrfConfig","crypto","socket","tryCatch","path","tryCatchSync","fs","tryCatch","tryCatchSync","reporter","tryCatchSync","tryCatch","layers"]}
|