From cfe2472610f5d28e6629e09321b8a017360005d2 Mon Sep 17 00:00:00 2001 From: Vladimir Rogojin Date: Sun, 3 May 2026 10:46:18 +0200 Subject: [PATCH 1/2] test(e2e-live): preflight gate via @unicitylabs/infra-probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a vitest globalSetup that runs the infra-probe against testnet (or mainnet/dev via env override) before any test file is loaded. If any service is unreachable the suite aborts up-front instead of burning a 10-15-minute container spawn cycle to surface the same failure as an opaque timeout downstream. Knobs: TRADER_E2E_SKIP_PREFLIGHT=1 — bypass entirely (escape hatch) TRADER_E2E_PREFLIGHT_STRICT=1 — also fail on degraded (default warns) TRADER_E2E_PREFLIGHT_NETWORK — override network (default: testnet) TRADER_E2E_PREFLIGHT_TIMEOUT_MS — per-probe ceiling (default: 30000) Default policy: fail-fast on `unreachable`, warn-and-proceed on `degraded`. The e2e suite has generous timeouts that absorb mild slowness, but a fully-down service guarantees a multi-minute hang. The strict mode is available for CI runs that prefer to surface degradation as failure rather than risk flaky test output. Adds two npm scripts (preflight, preflight:json) for ad-hoc probing without invoking vitest. Pulls in @unicitylabs/infra-probe@^0.3.0 from npm; ships a tiny .d.ts shim for the upstream pure-ESM module. Smoke-tested locally: helper unit-test file runs preflight then proceeds; SKIP env disables the gate; STRICT env elevates degraded to hard failure (probe currently caught a real 12s search degradation on the testnet market API). --- package-lock.json | 65 ++++++++++++++++ package.json | 3 + test/e2e-live/global-setup.ts | 14 ++++ test/e2e-live/infra-probe.d.ts | 17 +++++ test/e2e-live/preflight.ts | 135 +++++++++++++++++++++++++++++++++ vitest.e2e-live.config.ts | 6 ++ 6 files changed, 240 insertions(+) create mode 100644 test/e2e-live/global-setup.ts create mode 100644 test/e2e-live/infra-probe.d.ts create mode 100644 test/e2e-live/preflight.ts diff --git a/package-lock.json b/package-lock.json index 213e642..e4f83c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "@unicitylabs/trader-service", "version": "0.1.0", "dependencies": { + "@unicitylabs/infra-probe": "^0.3.0", "@unicitylabs/sphere-sdk": "file:../sphere-sdk", "commander": "^12.1.0", "uuid": "^11.1.0" @@ -955,6 +956,33 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1598,6 +1626,22 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@unicitylabs/infra-probe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@unicitylabs/infra-probe/-/infra-probe-0.3.0.tgz", + "integrity": "sha512-lul/bpMmJmguDchCxmJGaO/rBWjOs+dHbSP0brnxRbc/QvLF/tJqqZpvJIgqRMwQziWB4fWPvYu1cwqDFNcE6w==", + "license": "MIT", + "dependencies": { + "@noble/curves": "^1.6.0", + "ws": "^8.18.0" + }, + "bin": { + "unicity-infra-probe": "bin/unicity-infra-probe.mjs" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@unicitylabs/sphere-sdk": { "resolved": "../sphere-sdk", "link": true @@ -4237,6 +4281,27 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index f289a51..f0d7569 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,13 @@ "test:watch": "vitest", "test:coverage": "vitest run --coverage", "test:e2e-live": "vitest run --config vitest.e2e-live.config.ts", + "preflight": "unicity-infra-probe --network testnet", + "preflight:json": "unicity-infra-probe --network testnet --format json", "lint": "eslint .", "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json" }, "dependencies": { + "@unicitylabs/infra-probe": "^0.3.0", "@unicitylabs/sphere-sdk": "file:../sphere-sdk", "commander": "^12.1.0", "uuid": "^11.1.0" diff --git a/test/e2e-live/global-setup.ts b/test/e2e-live/global-setup.ts new file mode 100644 index 0000000..a4aa08d --- /dev/null +++ b/test/e2e-live/global-setup.ts @@ -0,0 +1,14 @@ +/** + * Vitest globalSetup for the e2e-live suite. + * + * Runs ONCE before any test file. If the preflight throws, vitest aborts + * the entire run before spawning any Docker containers — saving the + * 10-15-minute round trip we'd otherwise eat on a relay outage or + * unreachable aggregator. + */ + +import { runPreflight } from './preflight.js'; + +export async function setup(): Promise { + await runPreflight(); +} diff --git a/test/e2e-live/infra-probe.d.ts b/test/e2e-live/infra-probe.d.ts new file mode 100644 index 0000000..da3b901 --- /dev/null +++ b/test/e2e-live/infra-probe.d.ts @@ -0,0 +1,17 @@ +/** + * Ambient declaration for @unicitylabs/infra-probe — the package ships + * pure-ESM .mjs without bundled .d.ts. Only typed to the surface our + * preflight uses; the full report shape is re-typed locally in preflight.ts + * with `as Report` so changes upstream surface as type errors there. + */ +declare module '@unicitylabs/infra-probe' { + export interface ProbeOptions { + network?: 'testnet' | 'mainnet' | 'dev'; + only?: string[]; + timeoutMs?: number; + aggregatorApiKey?: string; + } + export function runProbes(options?: ProbeOptions): Promise; + export function exitCodeForReport(report: unknown): number; + export const SERVICES: readonly string[]; +} diff --git a/test/e2e-live/preflight.ts b/test/e2e-live/preflight.ts new file mode 100644 index 0000000..b85582e --- /dev/null +++ b/test/e2e-live/preflight.ts @@ -0,0 +1,135 @@ +/** + * Preflight infrastructure check for the e2e-live suite. + * + * Wraps `@unicitylabs/infra-probe` to verify that every Unicity Network + * service the e2e-live tests depend on (Nostr relay, L3 Aggregator, IPFS + * gateway, L1 Fulcrum, Market API) is reachable and functional BEFORE + * spawning any Docker containers or burning faucet quota. + * + * Failure modes the probe catches that the e2e suite would otherwise hit + * as opaque timeouts 5-15 minutes deep into a run: + * - Nostr relay silently dropping kind:30078 / kind:1059 publishes + * (the symptom of the 2026-04-30 outage that motivated this gate) + * - Aggregator API key rejection / rate-limit + * - IPFS gateway 5xx + * - Fulcrum chain tip stale (L1 stuck) → token-create races + * - Market API down → no intent discovery at all + * + * Environment knobs: + * TRADER_E2E_SKIP_PREFLIGHT=1 — bypass the gate entirely (escape hatch) + * TRADER_E2E_PREFLIGHT_STRICT=1 — also fail on `degraded` (default: warn-only) + * TRADER_E2E_PREFLIGHT_NETWORK — override the network (default: testnet) + * TRADER_E2E_PREFLIGHT_TIMEOUT_MS — per-probe ceiling (default: 30000) + */ + +import { runProbes } from '@unicitylabs/infra-probe'; + +interface Check { + readonly name: string; + readonly status: 'pass' | 'warn' | 'fail'; + readonly latencyMs: number; + readonly message: string; +} + +interface Service { + readonly service: string; + readonly endpoint: string; + readonly status: 'healthy' | 'degraded' | 'unreachable' | 'error'; + readonly latencyMs: number; + readonly checks: Check[]; + readonly error?: string; +} + +interface Report { + readonly services: Service[]; + readonly summary: { + readonly total: number; + readonly healthy: number; + readonly degraded: number; + readonly unreachable: number; + }; +} + +function statusIcon(status: Service['status']): string { + if (status === 'healthy') return '✓'; + if (status === 'degraded') return '⚠'; + return '✗'; +} + +function checkIcon(status: Check['status']): string { + if (status === 'pass') return '✓'; + if (status === 'warn') return '⚠'; + return '✗'; +} + +function logReport(report: Report): void { + for (const svc of report.services) { + console.log( + `[preflight] ${statusIcon(svc.status)} ${svc.service.padEnd(11)} ${svc.endpoint} (${svc.status}, ${svc.latencyMs}ms)`, + ); + if (svc.error) { + console.log(`[preflight] error: ${svc.error}`); + } + for (const c of svc.checks) { + const icon = checkIcon(c.status); + console.log(`[preflight] ${icon} ${c.name.padEnd(20)} ${c.latencyMs}ms ${c.message}`); + } + } + const { total, healthy, degraded, unreachable } = report.summary; + console.log( + `[preflight] summary: ${healthy}/${total} healthy, ${degraded} degraded, ${unreachable} unreachable`, + ); +} + +export async function runPreflight(): Promise { + if (process.env['TRADER_E2E_SKIP_PREFLIGHT'] === '1') { + console.log('[preflight] SKIPPED (TRADER_E2E_SKIP_PREFLIGHT=1)'); + return; + } + + const network = (process.env['TRADER_E2E_PREFLIGHT_NETWORK'] ?? 'testnet') as + | 'testnet' + | 'mainnet' + | 'dev'; + const timeoutMs = Number(process.env['TRADER_E2E_PREFLIGHT_TIMEOUT_MS'] ?? '30000'); + const strict = process.env['TRADER_E2E_PREFLIGHT_STRICT'] === '1'; + + console.log( + `[preflight] probing ${network} infrastructure (timeout=${timeoutMs}ms, strict=${strict})...`, + ); + const startedAt = Date.now(); + + const report = (await runProbes({ network, timeoutMs })) as Report; + const elapsed = Date.now() - startedAt; + + logReport(report); + console.log(`[preflight] completed in ${elapsed}ms`); + + const { unreachable, degraded } = report.summary; + const downServices = report.services + .filter((s) => s.status === 'unreachable' || s.status === 'error') + .map((s) => `${s.service}=${s.status}`); + const slowServices = report.services + .filter((s) => s.status === 'degraded') + .map((s) => `${s.service}=${s.status}`); + + if (unreachable > 0) { + throw new Error( + `Preflight failed: ${unreachable} service(s) unreachable [${downServices.join(', ')}]. ` + + `Set TRADER_E2E_SKIP_PREFLIGHT=1 to bypass (not recommended — tests will likely hang).`, + ); + } + + if (degraded > 0) { + if (strict) { + throw new Error( + `Preflight failed (strict mode): ${degraded} service(s) degraded [${slowServices.join(', ')}]. ` + + `Unset TRADER_E2E_PREFLIGHT_STRICT or set TRADER_E2E_SKIP_PREFLIGHT=1 to bypass.`, + ); + } + console.warn( + `[preflight] WARNING: ${degraded} service(s) degraded [${slowServices.join(', ')}] — ` + + `tests may be slow or intermittently fail. Set TRADER_E2E_PREFLIGHT_STRICT=1 to fail-fast on this.`, + ); + } +} diff --git a/vitest.e2e-live.config.ts b/vitest.e2e-live.config.ts index bfcae6c..940a1c3 100644 --- a/vitest.e2e-live.config.ts +++ b/vitest.e2e-live.config.ts @@ -21,6 +21,12 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { include: ['test/e2e-live/**/*.test.ts'], + // Run @unicitylabs/infra-probe before any test file. Aborts the run + // up-front if the testnet Nostr relay / aggregator / IPFS / Fulcrum / + // market is unreachable, instead of consuming a 10-15-minute container + // spawn cycle to discover the same failure as an opaque timeout. + // Bypass: TRADER_E2E_SKIP_PREFLIGHT=1. + globalSetup: ['./test/e2e-live/global-setup.ts'], testTimeout: 180_000, hookTimeout: 300_000, pool: 'forks', From 359aa2991a8c88fafcf349283c560db8a416b14a Mon Sep 17 00:00:00 2001 From: Vladimir Rogojin Date: Sun, 3 May 2026 14:53:22 +0200 Subject: [PATCH 2/2] =?UTF-8?q?test(e2e-live):=20address=20PR=20#11=20revi?= =?UTF-8?q?ew=20feedback=20=E2=80=94=20env=20validation=20+=20devDependenc?= =?UTF-8?q?ies=20+=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three review-feedback items from #11: ## W1+W2: validate env-var inputs at preflight startup `Number('abc')` returns NaN; `Number('-1')` returns -1; `Number('0')` returns 0. All of these would propagate to the upstream probe's `setTimeout` and either fire immediately (NaN coerces to 1ms in Node) or never fire — producing misleading "preflight failed" results from a typo. Validate `TRADER_E2E_PREFLIGHT_TIMEOUT_MS` with `Number.isFinite() && > 0` and throw a clean error otherwise. Same hardening for `TRADER_E2E_PREFLIGHT_NETWORK`: was a blind cast to `'testnet' | 'mainnet' | 'dev'`. The upstream `runProbes` would also throw on unknown networks, but tightening locally makes the contract visible at trader-service startup and immune to upstream silent enum extensions. ## W5: move @unicitylabs/infra-probe to devDependencies The probe is only used from `test/e2e-live/` and the `preflight` / `preflight:json` npm scripts. End-users `npm install`ing trader-service as a binary (the `bin: trader-ctl` entrypoint) shouldn't pull in the probe + its transitive deps (@noble/curves, ws). Move to devDependencies; lockfile updated. ## W3: document the four env vars in README A developer hitting "preflight failed" needs to know about `TRADER_E2E_SKIP_PREFLIGHT=1` without grepping the source. README now has an "E2E live tests — preflight gate" section with a table of all four env vars + their defaults + ad-hoc probing instructions. ## Not addressed (deliberately deferred) - C1 (cosmetic): "N service(s) unreachable" log message can over-count when `error` and `unreachable` mix. The gate still fires correctly; the message phrasing is a separate cleanup. - W4 (per-file opt-out): no opt-out mechanism for vitest globalSetup per-file. The `TRADER_E2E_SKIP_PREFLIGHT` escape hatch is the documented workaround. ## Test plan - typecheck clean for the test/ tree (the pre-existing src/trader/main.ts errors belong to PR #12 and are fixed there) - helper unit tests pass; preflight runs and validates env vars correctly - `npm install` re-resolves dep graph after move; no breakage --- README.md | 22 ++++++++++++++++++++++ package-lock.json | 6 +++++- package.json | 2 +- test/e2e-live/preflight.ts | 33 ++++++++++++++++++++++++++++----- 4 files changed, 56 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7e39b31..945edcf 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,28 @@ commands. - [docs/configuration.md](docs/configuration.md) — env vars + strategy file layout - [docs/test-specification.md](docs/test-specification.md) — test plan (236 tests) +## E2E live tests — preflight gate + +`npm run test:e2e-live` runs against the testnet Nostr relay, L3 aggregator, +IPFS gateway, Fulcrum, and Market API. Before any container is spawned, the +suite probes those services via `@unicitylabs/infra-probe` and aborts up-front +if any are unreachable — saving 10–15-minute container-spawn cycles that would +otherwise surface infra outages as opaque test timeouts. + +| Env var | Default | Effect | +|---|---|---| +| `TRADER_E2E_SKIP_PREFLIGHT` | unset | Set to `1` to bypass the gate entirely (escape hatch when iterating on TS/test-only changes that don't need infra) | +| `TRADER_E2E_PREFLIGHT_STRICT` | unset | Set to `1` to also fail on `degraded` (default warns and proceeds — e2e timeouts absorb mild slowness) | +| `TRADER_E2E_PREFLIGHT_NETWORK` | `testnet` | One of `testnet`, `mainnet`, `dev`. Other values throw at startup | +| `TRADER_E2E_PREFLIGHT_TIMEOUT_MS` | `30000` | Per-probe ceiling. Must be a positive finite number; NaN/0/negative throw at startup | + +Ad-hoc probing without invoking vitest: + +```sh +npm run preflight # pretty-printed +npm run preflight:json # one-line JSON for scripting +``` + ## Status Restored from `pre-trader-cut-v1` tag of agentic-hosting (Phase b decoupling). diff --git a/package-lock.json b/package-lock.json index e4f83c3..0d77f7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,6 @@ "name": "@unicitylabs/trader-service", "version": "0.1.0", "dependencies": { - "@unicitylabs/infra-probe": "^0.3.0", "@unicitylabs/sphere-sdk": "file:../sphere-sdk", "commander": "^12.1.0", "uuid": "^11.1.0" @@ -21,6 +20,7 @@ "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^8.24.0", "@typescript-eslint/parser": "^8.24.0", + "@unicitylabs/infra-probe": "^0.3.0", "@vitest/coverage-v8": "^3.0.0", "eslint": "^9.20.0", "tsup": "^8.4.0", @@ -960,6 +960,7 @@ "version": "1.9.7", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "dev": true, "license": "MIT", "dependencies": { "@noble/hashes": "1.8.0" @@ -975,6 +976,7 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -1630,6 +1632,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/@unicitylabs/infra-probe/-/infra-probe-0.3.0.tgz", "integrity": "sha512-lul/bpMmJmguDchCxmJGaO/rBWjOs+dHbSP0brnxRbc/QvLF/tJqqZpvJIgqRMwQziWB4fWPvYu1cwqDFNcE6w==", + "dev": true, "license": "MIT", "dependencies": { "@noble/curves": "^1.6.0", @@ -4285,6 +4288,7 @@ "version": "8.20.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index f0d7569..80f0586 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json" }, "dependencies": { - "@unicitylabs/infra-probe": "^0.3.0", "@unicitylabs/sphere-sdk": "file:../sphere-sdk", "commander": "^12.1.0", "uuid": "^11.1.0" @@ -31,6 +30,7 @@ "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^8.24.0", "@typescript-eslint/parser": "^8.24.0", + "@unicitylabs/infra-probe": "^0.3.0", "@vitest/coverage-v8": "^3.0.0", "eslint": "^9.20.0", "tsup": "^8.4.0", diff --git a/test/e2e-live/preflight.ts b/test/e2e-live/preflight.ts index b85582e..784832e 100644 --- a/test/e2e-live/preflight.ts +++ b/test/e2e-live/preflight.ts @@ -87,11 +87,34 @@ export async function runPreflight(): Promise { return; } - const network = (process.env['TRADER_E2E_PREFLIGHT_NETWORK'] ?? 'testnet') as - | 'testnet' - | 'mainnet' - | 'dev'; - const timeoutMs = Number(process.env['TRADER_E2E_PREFLIGHT_TIMEOUT_MS'] ?? '30000'); + // Validate network enum locally rather than blind-cast. The upstream + // `runProbes` would also throw on an unknown network, but tightening here + // makes the contract local and immune to upstream silent enum extensions. + const VALID_NETWORKS = ['testnet', 'mainnet', 'dev'] as const; + type Network = (typeof VALID_NETWORKS)[number]; + const rawNetwork = process.env['TRADER_E2E_PREFLIGHT_NETWORK'] ?? 'testnet'; + if (!(VALID_NETWORKS as readonly string[]).includes(rawNetwork)) { + throw new Error( + `Preflight: invalid TRADER_E2E_PREFLIGHT_NETWORK="${rawNetwork}". ` + + `Must be one of: ${VALID_NETWORKS.join(', ')}.`, + ); + } + const network = rawNetwork as Network; + + // Validate timeoutMs. `Number('abc')` returns NaN; `Number('-1')` returns -1; + // `Number('0')` returns 0. All of these would propagate to the upstream + // probe's setTimeout and either fire immediately (NaN coerces to 1ms in Node) + // or never fire (0 = no timeout in setTimeout's contract). Both produce + // misleading "preflight failed" results from a typo. Reject loudly. + const rawTimeoutMs = process.env['TRADER_E2E_PREFLIGHT_TIMEOUT_MS'] ?? '30000'; + const timeoutMs = Number(rawTimeoutMs); + if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) { + throw new Error( + `Preflight: invalid TRADER_E2E_PREFLIGHT_TIMEOUT_MS="${rawTimeoutMs}". ` + + `Must be a positive finite number (milliseconds).`, + ); + } + const strict = process.env['TRADER_E2E_PREFLIGHT_STRICT'] === '1'; console.log(