@glasstrace/sdk 1.16.0 → 1.18.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/README.md +44 -3
- package/dist/async-context/index.cjs.map +1 -1
- package/dist/async-context/index.js +2 -2
- package/dist/{capture-error-B0txjNut.d.cts → capture-error-B8qiXFeC.d.cts} +2 -2
- package/dist/{capture-error-Dc01rYNR.d.ts → capture-error-BTI6mCH2.d.ts} +2 -2
- package/dist/{chunk-WOYJAG7H.js → chunk-BJOZBAP7.js} +3 -3
- package/dist/{chunk-AFTCLH77.js → chunk-CN5EP25B.js} +2 -2
- package/dist/{chunk-KM4UNN3Q.js → chunk-EVX6D2TX.js} +2 -2
- package/dist/{chunk-BGJKEFBN.js → chunk-M3QGJUEI.js} +2 -2
- package/dist/{chunk-GBVMPMVV.js → chunk-MKT54VEH.js} +2 -2
- package/dist/{chunk-GBVMPMVV.js.map → chunk-MKT54VEH.js.map} +1 -1
- package/dist/{chunk-OHSX224U.js → chunk-O7IJP2TQ.js} +2 -2
- package/dist/{chunk-T7B752NF.js → chunk-SONZOTBP.js} +11 -3
- package/dist/{chunk-T7B752NF.js.map → chunk-SONZOTBP.js.map} +1 -1
- package/dist/{chunk-774XIOZG.js → chunk-UOAG72NR.js} +2 -2
- package/dist/{chunk-DW3CZDS6.js → chunk-YKE6HJLW.js} +2 -2
- package/dist/{chunk-EWW3TZ52.js → chunk-Z2DSC3YI.js} +61 -11
- package/dist/{chunk-EWW3TZ52.js.map → chunk-Z2DSC3YI.js.map} +1 -1
- package/dist/cli/init.cjs +4 -4
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.js +7 -7
- package/dist/cli/mcp-add.cjs +1 -1
- package/dist/cli/mcp-add.cjs.map +1 -1
- package/dist/cli/mcp-add.js +3 -3
- package/dist/cli/uninit.js +3 -3
- package/dist/cli/upgrade-instructions.cjs +1 -1
- package/dist/cli/upgrade-instructions.js +3 -3
- package/dist/cli/validate.cjs.map +1 -1
- package/dist/cli/validate.js +2 -2
- package/dist/{correlation-id-CelUvw7j.d.cts → correlation-id-CClOq8Wn.d.cts} +1 -1
- package/dist/{correlation-id-B9YYmoZw.d.ts → correlation-id-Ct86Ug4s.d.ts} +1 -1
- package/dist/edge-entry.cjs.map +1 -1
- package/dist/edge-entry.d.cts +2 -2
- package/dist/edge-entry.d.ts +2 -2
- package/dist/edge-entry.js +4 -4
- package/dist/{import-graph-Dka_Fm7j.d.ts → import-graph-DZjTJdJ5.d.ts} +1 -1
- package/dist/{import-graph-DBLGNjcI.d.cts → import-graph-DyQfZU2f.d.cts} +1 -1
- package/dist/index.cjs +202 -7
- package/dist/index.cjs.map +1 -1
- package/dist/{index.d-3-cJoY8y.d.cts → index.d-DhatN7mq.d.cts} +1 -1
- package/dist/{index.d-3-cJoY8y.d.ts → index.d-DhatN7mq.d.ts} +1 -1
- package/dist/index.d.cts +243 -5
- package/dist/index.d.ts +243 -5
- package/dist/index.js +151 -6
- package/dist/index.js.map +1 -1
- package/dist/middleware/index.cjs.map +1 -1
- package/dist/middleware/index.js +2 -2
- package/dist/node-entry.cjs +5 -5
- package/dist/node-entry.cjs.map +1 -1
- package/dist/node-entry.d.cts +4 -4
- package/dist/node-entry.d.ts +4 -4
- package/dist/node-entry.js +7 -7
- package/dist/node-subpath.cjs.map +1 -1
- package/dist/node-subpath.d.cts +2 -2
- package/dist/node-subpath.d.ts +2 -2
- package/dist/node-subpath.js +3 -3
- package/dist/{source-map-uploader-UJPZCUFN.js → source-map-uploader-U7SLSKIZ.js} +3 -3
- package/dist/trpc/index.cjs.map +1 -1
- package/dist/trpc/index.js +1 -1
- package/package.json +1 -1
- /package/dist/{chunk-WOYJAG7H.js.map → chunk-BJOZBAP7.js.map} +0 -0
- /package/dist/{chunk-AFTCLH77.js.map → chunk-CN5EP25B.js.map} +0 -0
- /package/dist/{chunk-KM4UNN3Q.js.map → chunk-EVX6D2TX.js.map} +0 -0
- /package/dist/{chunk-BGJKEFBN.js.map → chunk-M3QGJUEI.js.map} +0 -0
- /package/dist/{chunk-OHSX224U.js.map → chunk-O7IJP2TQ.js.map} +0 -0
- /package/dist/{chunk-774XIOZG.js.map → chunk-UOAG72NR.js.map} +0 -0
- /package/dist/{chunk-DW3CZDS6.js.map → chunk-YKE6HJLW.js.map} +0 -0
- /package/dist/{source-map-uploader-UJPZCUFN.js.map → source-map-uploader-U7SLSKIZ.js.map} +0 -0
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
PresignedUploadResponseSchema,
|
|
6
6
|
SourceMapManifestResponseSchema,
|
|
7
7
|
SourceMapUploadResponseSchema
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-MKT54VEH.js";
|
|
9
9
|
|
|
10
10
|
// src/source-map-uploader.ts
|
|
11
11
|
import * as fs from "node:fs/promises";
|
|
@@ -331,4 +331,4 @@ export {
|
|
|
331
331
|
uploadSourceMapsPresigned,
|
|
332
332
|
uploadSourceMapsAuto
|
|
333
333
|
};
|
|
334
|
-
//# sourceMappingURL=chunk-
|
|
334
|
+
//# sourceMappingURL=chunk-O7IJP2TQ.js.map
|
|
@@ -2,12 +2,12 @@ import {
|
|
|
2
2
|
atomicWriteFile,
|
|
3
3
|
refreshGenericMcpConfigAtRuntime,
|
|
4
4
|
resolveEffectiveMcpCredential
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-UOAG72NR.js";
|
|
6
6
|
import {
|
|
7
7
|
DEFAULT_CAPTURE_CONFIG,
|
|
8
8
|
SdkCachedConfigSchema,
|
|
9
9
|
SdkInitResponseSchema
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-MKT54VEH.js";
|
|
11
11
|
import {
|
|
12
12
|
__require
|
|
13
13
|
} from "./chunk-NSBPE2FW.js";
|
|
@@ -630,6 +630,13 @@ function getActiveConfig() {
|
|
|
630
630
|
}
|
|
631
631
|
return { ...DEFAULT_CAPTURE_CONFIG };
|
|
632
632
|
}
|
|
633
|
+
function isCaptureEnabled() {
|
|
634
|
+
try {
|
|
635
|
+
return getActiveConfig().sideEffectEvidence === true;
|
|
636
|
+
} catch {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
633
640
|
function getLinkedAccountId() {
|
|
634
641
|
return currentConfig?.linkedAccountId;
|
|
635
642
|
}
|
|
@@ -685,6 +692,7 @@ export {
|
|
|
685
692
|
sendInitRequest,
|
|
686
693
|
performInit,
|
|
687
694
|
getActiveConfig,
|
|
695
|
+
isCaptureEnabled,
|
|
688
696
|
getLinkedAccountId,
|
|
689
697
|
getClaimResult,
|
|
690
698
|
_setCurrentConfig,
|
|
@@ -692,4 +700,4 @@ export {
|
|
|
692
700
|
didLastInitSucceed,
|
|
693
701
|
verifyInitReachable
|
|
694
702
|
};
|
|
695
|
-
//# sourceMappingURL=chunk-
|
|
703
|
+
//# sourceMappingURL=chunk-SONZOTBP.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/health-collector.ts","../src/https-transport.ts","../src/init-client.ts"],"sourcesContent":["import type { SdkHealthReport } from \"@glasstrace/protocol\";\n\n// --- Module-level state (singleton pattern matching init-client.ts) ---\n\n/** Spans successfully forwarded to the delegate exporter since last collect. */\nlet tracesExported = 0;\n\n/** Spans dropped: buffer overflow evictions + spans lost on shutdown. */\nlet tracesDropped = 0;\n\n/** Failed performInit attempts since last collect. */\nlet initFailures = 0;\n\n/** Timestamp (ms) of the last successful config sync (performInit success or cached config load). */\nlet lastConfigSyncAt: number | null = null;\n\n// --- Recording functions (called by other modules) ---\n\n/**\n * Records that spans were submitted to the delegate exporter.\n * Counts submission, not confirmed delivery (DISC-1118).\n * Called by GlasstraceExporter after delegate.export() is invoked.\n */\nexport function recordSpansExported(count: number): void {\n if (!Number.isFinite(count) || count < 0 || !Number.isInteger(count)) return;\n tracesExported += count;\n}\n\n/**\n * Records that spans were dropped (buffer overflow eviction or shutdown loss).\n * Called by GlasstraceExporter on buffer eviction and unresolved-key shutdown.\n */\nexport function recordSpansDropped(count: number): void {\n if (!Number.isFinite(count) || count < 0 || !Number.isInteger(count)) return;\n tracesDropped += count;\n}\n\n/**\n * Records a failed performInit attempt.\n * Called by performInit when the init request fails for any reason.\n */\nexport function recordInitFailure(): void {\n try { initFailures += 1; } catch { /* best-effort */ }\n}\n\n/**\n * Records the timestamp of a successful config sync.\n * Called by performInit on success and by loadCachedConfig when loading a valid cache.\n */\nexport function recordConfigSync(timestamp: number): void {\n try { lastConfigSyncAt = timestamp; } catch { /* best-effort */ }\n}\n\n// --- Collection ---\n\n/**\n * Snapshots the current health metrics into an SdkHealthReport without\n * resetting counters. Counters are only reset when {@link acknowledgeHealthReport}\n * is called after the init request succeeds. This two-phase approach prevents\n * metric loss when `performInit` fails — the counters persist for the next\n * init attempt.\n *\n * On the first init call, all counters will be zero, which is correct.\n *\n * @param sdkVersion - The SDK version string to include in the report.\n * @returns The health report, or null if collection fails unexpectedly.\n */\nexport function collectHealthReport(sdkVersion: string): SdkHealthReport | null {\n try {\n const now = Date.now();\n const configAge = lastConfigSyncAt !== null ? Math.max(0, now - lastConfigSyncAt) : 0;\n\n return {\n tracesExportedSinceLastInit: tracesExported,\n tracesDropped,\n initFailures,\n configAge: Math.round(configAge),\n sdkVersion,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Subtracts the reported values from the running counters after a health\n * report has been successfully delivered to the backend. Called by\n * `performInit` on the success path. If init fails, counters persist\n * for the next attempt.\n *\n * Uses subtraction instead of zeroing to preserve any increments that\n * occurred between the snapshot (`collectHealthReport`) and delivery\n * (e.g., spans exported during the init HTTP call). Values are clamped\n * to 0 to guard against edge cases.\n *\n * Core invariant (DISC-1123): for any finite counter C and finite reported\n * value, after acknowledge:\n * C_new = max(0, C_before_ack - reported_value)\n * This guarantees:\n * 1. Reported finite, non-negative values are removed exactly once\n * (no double-counting).\n * 2. Activity between snapshot and acknowledge is preserved (not lost).\n * 3. The counter never goes negative (clamp prevents underflow).\n * Corruption vectors guarded: non-finite report fields (NaN/±Infinity)\n * preserve the counter; negative finite report fields are clamped to 0\n * before subtraction.\n *\n * `lastConfigSyncAt` is NOT affected — config age measures time since\n * the last successful sync, not the last acknowledgment.\n */\nexport function acknowledgeHealthReport(report: SdkHealthReport): void {\n const exp = Math.max(0, report.tracesExportedSinceLastInit);\n const expVal = tracesExported - exp;\n tracesExported = Number.isFinite(expVal) ? Math.max(0, expVal) : tracesExported;\n\n const drop = Math.max(0, report.tracesDropped);\n const dropVal = tracesDropped - drop;\n tracesDropped = Number.isFinite(dropVal) ? Math.max(0, dropVal) : tracesDropped;\n\n const fail = Math.max(0, report.initFailures);\n const failVal = initFailures - fail;\n initFailures = Number.isFinite(failVal) ? Math.max(0, failVal) : initFailures;\n}\n\n// --- Test support ---\n\n/**\n * Resets all health metrics to initial state. For testing only.\n */\nexport function _resetHealthForTesting(): void {\n tracesExported = 0;\n tracesDropped = 0;\n initFailures = 0;\n lastConfigSyncAt = null;\n}\n","/**\n * Minimal Node-native HTTPS transport used by the SDK init call.\n *\n * ## Why this exists\n *\n * Next.js 16 patches the global `fetch` to add caching, revalidation,\n * and request deduplication. When the SDK is bundled into a Next.js\n * process (instrumentation.ts path), outbound calls to\n * `api.glasstrace.dev` get intercepted by the patched fetch and can\n * silently hang — the fetch promise never resolves (DISC-493 Issue 3).\n *\n * A silent init hang is catastrophic: the SDK stays in \"pending key\"\n * state forever, enriched spans are buffered without ever being\n * exported, and anonymous keys are never registered server-side\n * (DISC-494).\n *\n * ## Why `node:https`\n *\n * `node:https` is a Node.js core module. It has zero bundle weight\n * (important because the SDK is tsup-inlined into every consumer's\n * bundle) and is always available on Node.js >= 20. Using it directly\n * bypasses the global `fetch` patching entirely — Next.js never sees\n * the request.\n *\n * Alternatives considered and rejected:\n *\n * - **`undici` as a runtime dep** — adds ~400KB inlined into every\n * consumer bundle.\n * - **`fetch(..., { cache: \"no-store\", next: { revalidate: 0 } })`** —\n * bandaid. Couples the SDK to Next.js's fetch-extension API and still\n * relies on Next's patched fetch behaving correctly. Explicitly\n * forbidden by the task brief for this reason.\n * - **Monkey-patch `globalThis.fetch`** — forbidden in the public SDK\n * (`glasstrace-sdk/CLAUDE.md`). Bypassing the patched fetch by\n * calling a different API is avoidance, not patching.\n *\n * ## Structure\n *\n * `httpsPostJson` is the only exported function. It:\n * - Sends a POST to a URL with a JSON body\n * - Applies a per-request timeout (default 10s)\n * - Retries transport-level failures (DNS, TCP, TLS) with backoff\n * - Never retries HTTP status errors — those are surfaced immediately\n * - Distinguishes transport failure, server error, and body-parse error\n * so callers can render actionable messages\n */\nimport {\n request as httpsRequest,\n type RequestOptions as HttpsRequestOptions,\n} from \"node:https\";\nimport {\n request as httpRequest,\n type IncomingMessage,\n} from \"node:http\";\nimport { URL } from \"node:url\";\n\n/** Error thrown when the HTTP request never completed (DNS/TCP/TLS/timeout). */\nexport class HttpsTransportError extends Error {\n readonly kind = \"transport\" as const;\n readonly cause?: unknown;\n constructor(message: string, cause?: unknown) {\n super(message);\n this.name = \"HttpsTransportError\";\n this.cause = cause;\n }\n}\n\n/** Error thrown when the server returned a non-2xx HTTP status. */\nexport class HttpsStatusError extends Error {\n readonly kind = \"status\" as const;\n readonly status: number;\n /** Raw response body text (may be truncated by caller if large). */\n readonly body: string;\n constructor(status: number, body: string) {\n super(`Server returned HTTP ${status}`);\n this.name = \"HttpsStatusError\";\n this.status = status;\n this.body = body;\n }\n}\n\n/** Error thrown when the response body was not parseable JSON. */\nexport class HttpsBodyParseError extends Error {\n readonly kind = \"parse\" as const;\n readonly status: number;\n readonly cause?: unknown;\n constructor(status: number, cause?: unknown) {\n super(`Server returned malformed response (HTTP ${status})`);\n this.name = \"HttpsBodyParseError\";\n this.status = status;\n this.cause = cause;\n }\n}\n\n/** Options controlling timeout and retry behavior. */\nexport interface HttpsPostJsonOptions {\n /** Parsed headers (including Content-Type, Authorization, etc). */\n headers: Record<string, string>;\n /** Per-attempt timeout, ms. Defaults to 10000. */\n timeoutMs?: number;\n /**\n * Total number of attempts INCLUDING the first. Defaults to 3\n * (initial + 2 retries). Only transport errors are retried.\n */\n maxAttempts?: number;\n /**\n * Backoff delays between retries, ms. The array length should be\n * `maxAttempts - 1`. Defaults to [500, 1500].\n */\n retryDelaysMs?: readonly number[];\n /**\n * Total deadline across all attempts, ms. Defaults to 20000.\n * If exceeded, no further retries are attempted and the last error\n * is surfaced. Guards against CLI hang on flaky networks.\n */\n totalDeadlineMs?: number;\n /**\n * Abort signal. When aborted, the in-flight request is terminated and\n * no further retries are attempted.\n */\n signal?: AbortSignal;\n /**\n * Scheduler injection point for tests. Defaults to `setTimeout`.\n * Using fake timers in tests requires the injected scheduler to honor\n * `vi.advanceTimersByTime()` — Node's real setTimeout is fine when no\n * fake timers are installed.\n */\n scheduler?: (fn: () => void, ms: number) => { unref?: () => void };\n /**\n * Alternate HTTPS request function. Injected by tests to simulate\n * Next.js-style fetch patching (assert call count stays zero) or to\n * mock transport behavior without opening real sockets.\n */\n requestImpl?: typeof httpsRequest;\n /**\n * Alternate HTTP request function, used when the URL is `http://`.\n * Splitting http/https lets tests use a local non-TLS mock server.\n */\n httpRequestImpl?: typeof httpRequest;\n}\n\n/** Shape of a successful response. */\nexport interface HttpsPostJsonResult {\n /** HTTP status code. Always in [200, 299] for success. */\n status: number;\n /** Parsed JSON body. May be `undefined` for 204 No Content. */\n body: unknown;\n /** Raw body text for diagnostics. */\n raw: string;\n}\n\n/** Delays so a failing test still completes before the suite's timeout. */\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_RETRY_DELAYS_MS = [500, 1500] as const;\nconst DEFAULT_TOTAL_DEADLINE_MS = 20_000;\n\n/**\n * Sends a POST request with a JSON body using `node:https`. Bypasses\n * any `globalThis.fetch` patching (Next.js 16, MSW, etc).\n *\n * @throws {HttpsTransportError} DNS failure, TCP reset, TLS handshake\n * failure, request timeout, or abort.\n * @throws {HttpsStatusError} HTTP response with status >= 400.\n * @throws {HttpsBodyParseError} HTTP 2xx with non-JSON body (status not\n * equal to 204).\n */\nexport async function httpsPostJson(\n url: string,\n jsonBody: unknown,\n options: HttpsPostJsonOptions,\n): Promise<HttpsPostJsonResult> {\n const parsed = new URL(url);\n const isHttps = parsed.protocol === \"https:\";\n const isHttp = parsed.protocol === \"http:\";\n if (!isHttps && !isHttp) {\n throw new HttpsTransportError(\n `Unsupported protocol: ${parsed.protocol} (expected http: or https:)`,\n );\n }\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const maxAttempts = options.maxAttempts ?? 3;\n const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;\n const totalDeadlineMs = options.totalDeadlineMs ?? DEFAULT_TOTAL_DEADLINE_MS;\n const scheduler = options.scheduler ?? ((fn, ms) => setTimeout(fn, ms));\n const requestImpl = isHttps\n ? (options.requestImpl ?? httpsRequest)\n : (options.httpRequestImpl ?? httpRequest);\n\n // Serialize once so retries use the exact same bytes.\n let payload: string;\n try {\n payload = JSON.stringify(jsonBody);\n } catch (err) {\n throw new HttpsTransportError(\n `Failed to serialize request body: ${err instanceof Error ? err.message : String(err)}`,\n err,\n );\n }\n const payloadBuffer = Buffer.from(payload, \"utf-8\");\n\n const startedAt = Date.now();\n let lastError: unknown;\n\n for (let attempt = 0; attempt < maxAttempts; attempt += 1) {\n if (options.signal?.aborted) {\n throw new HttpsTransportError(\"Request aborted\");\n }\n // Respect the total deadline — don't start another attempt if we'd\n // blow past it even before issuing the request.\n const elapsed = Date.now() - startedAt;\n if (elapsed >= totalDeadlineMs) {\n break;\n }\n const remainingBudget = totalDeadlineMs - elapsed;\n const attemptTimeoutMs = Math.min(timeoutMs, remainingBudget);\n\n try {\n return await sendSingleRequest(\n parsed,\n payloadBuffer,\n options.headers,\n attemptTimeoutMs,\n options.signal,\n requestImpl,\n );\n } catch (err) {\n lastError = err;\n // Never retry status/parse errors — they're server responses, not\n // transient network failures.\n if (err instanceof HttpsStatusError || err instanceof HttpsBodyParseError) {\n throw err;\n }\n const isLast = attempt === maxAttempts - 1;\n if (isLast) break;\n\n const delayMs = retryDelaysMs[attempt] ?? retryDelaysMs[retryDelaysMs.length - 1] ?? 0;\n const elapsedBeforeSleep = Date.now() - startedAt;\n const remaining = totalDeadlineMs - elapsedBeforeSleep;\n if (remaining <= 0) break;\n const actualDelayMs = Math.min(delayMs, remaining);\n await sleep(actualDelayMs, scheduler, options.signal);\n }\n }\n\n if (lastError instanceof HttpsTransportError) throw lastError;\n throw new HttpsTransportError(\n lastError instanceof Error ? lastError.message : \"Request failed\",\n lastError,\n );\n}\n\n/**\n * Fires a single HTTPS request. Resolves with the parsed result on 2xx,\n * throws the appropriate typed error otherwise. Caller handles retries.\n */\nfunction sendSingleRequest(\n url: URL,\n payload: Buffer,\n headers: Record<string, string>,\n timeoutMs: number,\n signal: AbortSignal | undefined,\n requestImpl: typeof httpsRequest,\n): Promise<HttpsPostJsonResult> {\n return new Promise<HttpsPostJsonResult>((resolve, reject) => {\n // Merge caller headers with Content-Length so Node doesn't chunk\n // the body. Explicit content-length also prevents confusion from\n // servers that reject chunked POSTs.\n const finalHeaders: Record<string, string | number> = {\n ...headers,\n \"Content-Length\": payload.byteLength,\n };\n\n const reqOptions: HttpsRequestOptions = {\n method: \"POST\",\n hostname: url.hostname,\n port: url.port === \"\" ? undefined : Number(url.port),\n path: `${url.pathname}${url.search}`,\n headers: finalHeaders,\n // Explicit timeout at the socket level. Still complemented by a\n // manual timer below because `timeout` only fires when the socket\n // is idle — it does not cover \"TLS handshake hangs forever\".\n timeout: timeoutMs,\n };\n\n let settled = false;\n // Hoisted so every settle path can clear the manual timer and drop\n // the optional abort listener. Assigned below once `req`, `timer`,\n // and `onAbort` exist.\n let cleanup = (): void => {};\n const settle = (fn: () => void): void => {\n if (settled) return;\n settled = true;\n cleanup();\n fn();\n };\n\n const req = requestImpl(reqOptions, (res: IncomingMessage) => {\n const chunks: Buffer[] = [];\n res.on(\"data\", (chunk: Buffer | string) => {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk, \"utf-8\") : chunk);\n });\n res.on(\"end\", () => {\n const raw = Buffer.concat(chunks).toString(\"utf-8\");\n const status = res.statusCode ?? 0;\n if (status < 200 || status >= 300) {\n settle(() => reject(new HttpsStatusError(status, raw)));\n return;\n }\n // HTTP 204: no body is expected; resolve with undefined.\n if (status === 204 || raw.length === 0) {\n settle(() => resolve({ status, body: undefined, raw }));\n return;\n }\n try {\n const parsed = JSON.parse(raw);\n settle(() => resolve({ status, body: parsed, raw }));\n } catch (err) {\n settle(() => reject(new HttpsBodyParseError(status, err)));\n }\n });\n res.on(\"error\", (err) => {\n settle(() => reject(new HttpsTransportError(`Response stream error: ${err.message}`, err)));\n });\n });\n\n // A single manual timeout guards against handshake/DNS hangs that\n // the `timeout` option in `request()` does not cover. We destroy the\n // socket on timeout so the node:https layer doesn't keep it alive.\n const timer = setTimeout(() => {\n settle(() => {\n req.destroy(new Error(\"Request timed out\"));\n reject(new HttpsTransportError(`Request timed out after ${timeoutMs}ms`));\n });\n }, timeoutMs);\n // Don't block process exit while the timer is running.\n if (typeof timer.unref === \"function\") timer.unref();\n\n // Hoisted so `cleanup` can remove it on settle. Only registered\n // on the signal below when `signal !== undefined`.\n const onAbort = (): void => {\n settle(() => {\n req.destroy(new Error(\"Aborted\"));\n reject(new HttpsTransportError(\"Request aborted\"));\n });\n };\n\n // Install cleanup now that `timer` and `onAbort` exist. Invoked by\n // every settle path (success, status-error, parse-error, transport\n // error, timeout, abort) to clear the manual timer and drop the\n // abort listener so long-lived signals don't accumulate listeners.\n cleanup = (): void => {\n clearTimeout(timer);\n if (signal !== undefined) {\n signal.removeEventListener(\"abort\", onAbort);\n }\n };\n\n req.on(\"error\", (err) => {\n settle(() => reject(new HttpsTransportError(`fetch failed: ${err.message}`, err)));\n });\n\n req.on(\"timeout\", () => {\n settle(() => {\n req.destroy(new Error(\"Request timed out\"));\n reject(new HttpsTransportError(`Request timed out after ${timeoutMs}ms`));\n });\n });\n\n if (signal !== undefined) {\n if (signal.aborted) {\n req.destroy(new Error(\"Aborted\"));\n settle(() => reject(new HttpsTransportError(\"Request aborted\")));\n return;\n }\n signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n\n req.end(payload);\n });\n}\n\n/**\n * Delay helper that honors an AbortSignal. We cannot use `setTimeout`'s\n * built-in `signal` option because it is not available in older Node 20\n * patch releases (added in 20.6).\n *\n * Both settle paths (timer fires, signal aborts) clear the pending\n * timer and remove the abort listener so this helper remains leak-free\n * under heavy retry/abort usage.\n */\nfunction sleep(\n ms: number,\n scheduler: (fn: () => void, ms: number) => { unref?: () => void },\n signal: AbortSignal | undefined,\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n let settled = false;\n // Forward-declared so `settle` below can reference it. Assigned\n // once `timer` exists.\n let cleanup = (): void => {};\n const settle = (fn: () => void): void => {\n if (settled) return;\n settled = true;\n cleanup();\n fn();\n };\n const onAbort = (): void => {\n settle(() => reject(new HttpsTransportError(\"Request aborted\")));\n };\n const timer = scheduler(() => {\n settle(resolve);\n }, ms);\n if (typeof timer.unref === \"function\") timer.unref();\n cleanup = (): void => {\n // `scheduler` returns whatever `setTimeout` returns (NodeJS.Timeout\n // in Node, number in jsdom). Both are accepted by `clearTimeout`.\n clearTimeout(timer as unknown as NodeJS.Timeout);\n if (signal !== undefined) {\n signal.removeEventListener(\"abort\", onAbort);\n }\n };\n if (signal !== undefined) {\n if (signal.aborted) {\n onAbort();\n return;\n }\n signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n });\n}\n","import {\n SdkInitResponseSchema,\n SdkCachedConfigSchema,\n DEFAULT_CAPTURE_CONFIG,\n} from \"@glasstrace/protocol\";\nimport type {\n SdkInitResponse,\n CaptureConfig,\n AnonApiKey,\n ImportGraphPayload,\n SdkHealthReport,\n SdkDiagnosticCode,\n} from \"@glasstrace/protocol\";\nimport type { ResolvedConfig } from \"./env-detection.js\";\nimport { recordInitFailure, recordConfigSync, acknowledgeHealthReport } from \"./health-collector.js\";\nimport {\n httpsPostJson,\n HttpsStatusError,\n HttpsTransportError,\n HttpsBodyParseError,\n} from \"./https-transport.js\";\nimport {\n resolveEffectiveMcpCredential,\n refreshGenericMcpConfigAtRuntime,\n} from \"./mcp-runtime.js\";\nimport { atomicWriteFile } from \"./atomic-write.js\";\n\nconst GLASSTRACE_DIR = \".glasstrace\";\nconst CONFIG_FILE = \"config\";\nconst TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000;\nconst INIT_TIMEOUT_MS = 10_000;\n\n/**\n * Lazily imports `node:fs/promises` and `node:path`. Returns `null` if\n * the modules are unavailable (non-Node environments). Cached after first call.\n */\nlet fsPathAsyncCache: { fs: typeof import(\"node:fs/promises\"); path: typeof import(\"node:path\") } | null | undefined;\n\nasync function loadFsPathAsync(): Promise<{ fs: typeof import(\"node:fs/promises\"); path: typeof import(\"node:path\") } | null> {\n if (fsPathAsyncCache !== undefined) return fsPathAsyncCache;\n try {\n const [fs, path] = await Promise.all([\n import(\"node:fs/promises\"),\n import(\"node:path\"),\n ]);\n fsPathAsyncCache = { fs, path };\n return fsPathAsyncCache;\n } catch {\n fsPathAsyncCache = null;\n return null;\n }\n}\n\n/**\n * Lazily imports synchronous `node:fs` and `node:path` via `require()`.\n * Returns `null` when unavailable. Used by `loadCachedConfig` which is\n * synchronous for startup performance.\n */\nfunction loadFsSyncOrNull(): { readFileSync: typeof import(\"node:fs\").readFileSync; join: typeof import(\"node:path\").join } | null {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, glasstrace/no-unguarded-node-require -- guarded by the surrounding try/catch in `loadFsSyncOrNull`; throw returns `null`, callers (e.g. `loadCachedConfig`) treat it as no cached config (DISC-1555).\n const fs = require(\"node:fs\") as typeof import(\"node:fs\");\n // eslint-disable-next-line @typescript-eslint/no-require-imports, glasstrace/no-unguarded-node-require -- guarded by the same try/catch as the preceding `node:fs` require (DISC-1555).\n const path = require(\"node:path\") as typeof import(\"node:path\");\n return { readFileSync: fs.readFileSync, join: path.join };\n } catch {\n return null;\n }\n}\n\n/**\n * Test-only transport hook. When set, `sendInitRequest` calls this\n * instead of `httpsPostJson`. Enables unit tests to assert that the\n * SDK never routes through `globalThis.fetch` (Next.js patching) by\n * injecting a pure-function transport that never touches the network.\n *\n * Production code never sets this. Reset via `_resetConfigForTesting()`.\n */\ntype HttpsPostJsonFn = typeof httpsPostJson;\nlet transportOverride: HttpsPostJsonFn | null = null;\n\n/** In-memory config from the latest successful init response. */\nlet currentConfig: SdkInitResponse | null = null;\n\n/** Whether the disk cache has already been checked by getActiveConfig(). */\nlet configCacheChecked = false;\n\n/** Whether the next init call should be skipped (rate-limit backoff). */\nlet rateLimitBackoff = false;\n\n/** Whether the most recent performInit call completed the success path. */\nlet lastInitSucceeded = false;\n\n/**\n * Reads and validates a cached config file from `.glasstrace/config`.\n * Returns the parsed `SdkInitResponse` or `null` on any failure,\n * including when `node:fs` is unavailable (non-Node environments).\n */\nexport function loadCachedConfig(projectRoot?: string): SdkInitResponse | null {\n const modules = loadFsSyncOrNull();\n if (!modules) return null;\n\n const root = projectRoot ?? process.cwd();\n const configPath = modules.join(root, GLASSTRACE_DIR, CONFIG_FILE);\n\n try {\n // Use synchronous read for startup performance (this is called during init)\n const content = modules.readFileSync(configPath, \"utf-8\");\n const parsed = JSON.parse(content);\n const cached = SdkCachedConfigSchema.parse(parsed);\n\n // Warn if cache is stale\n const age = Date.now() - cached.cachedAt;\n if (age > TWENTY_FOUR_HOURS_MS) {\n console.warn(\n `[glasstrace] Cached config is ${Math.round(age / 3600000)}h old. Will refresh on next init.`,\n );\n }\n\n // Parse the response through the schema\n const result = SdkInitResponseSchema.safeParse(cached.response);\n if (result.success) {\n recordConfigSync(cached.cachedAt);\n return result.data;\n }\n\n console.warn(\"[glasstrace] Cached config failed validation. Using defaults.\");\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Persists the init response to `.glasstrace/config` using the SDK 2.0\n * atomic-write protocol (`tmp + fsync(tmp) + rename + fsync(parent)`).\n * Silently skipped when `node:fs` is unavailable (non-Node environments).\n * On I/O failure, logs a warning.\n *\n * Atomicity: the payload is written to `.glasstrace/config.tmp`, fsynced\n * to durable storage, then renamed into place; the parent directory is\n * fsynced last so the rename survives an immediate crash. `rename` is\n * atomic on POSIX filesystems, so readers either see the previous valid\n * config or the new valid config — never a truncated or partially-written\n * file (DISC-1247 Scenario 5). If any step fails, the temp file is\n * cleaned up on a best-effort basis.\n */\nexport async function saveCachedConfig(\n response: SdkInitResponse,\n projectRoot?: string,\n): Promise<void> {\n const modules = await loadFsPathAsync();\n if (!modules) return;\n\n const root = projectRoot ?? process.cwd();\n const dirPath = modules.path.join(root, GLASSTRACE_DIR);\n const configPath = modules.path.join(dirPath, CONFIG_FILE);\n\n try {\n await modules.fs.mkdir(dirPath, { recursive: true, mode: 0o700 });\n await modules.fs.chmod(dirPath, 0o700);\n const cached = {\n response,\n cachedAt: Date.now(),\n };\n // Atomic write per SDK 2.0 §4.3: tmp + fsync(tmp) + rename +\n // fsync(parent). Sibling temp guarantees same-filesystem rename\n // (atomic per POSIX). The helper best-effort cleans up the tmp\n // file on failure (DISC-1247 Scenario 5).\n await atomicWriteFile(configPath, JSON.stringify(cached), {\n encoding: \"utf-8\",\n mode: 0o600,\n });\n // chmod the final path to defend against platforms that don't honor\n // the mode passed to writeFile/rename on first creation.\n await modules.fs.chmod(configPath, 0o600);\n } catch (err) {\n console.warn(\n `[glasstrace] Failed to cache config to ${configPath}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\n/**\n * Sends a POST request to `/v1/sdk/init`.\n * Validates the response against `SdkInitResponseSchema`.\n *\n * Uses `node:https` via {@link httpsPostJson} rather than the global\n * `fetch` because Next.js 16 patches `fetch` for caching/revalidation\n * and can cause the init request to silently hang (DISC-493 Issue 3).\n * Retries transport-level failures (DNS, TCP, TLS) twice with 500ms +\n * 1500ms backoff, capped at a 20-second total deadline. Server responses\n * (HTTP 4xx/5xx) are never retried and are surfaced immediately.\n */\nexport async function sendInitRequest(\n config: ResolvedConfig,\n anonKey: AnonApiKey | null,\n sdkVersion: string,\n importGraph?: ImportGraphPayload,\n healthReport?: SdkHealthReport,\n diagnostics?: Array<{ code: SdkDiagnosticCode; message: string; timestamp: number }>,\n signal?: AbortSignal,\n): Promise<SdkInitResponse> {\n // Determine the API key for auth. Use || (not ??) so empty strings\n // fall through to the anonymous key — defense in depth for DISC-467.\n const effectiveKey = config.apiKey || anonKey;\n if (!effectiveKey) {\n throw new Error(\"No API key available for init request\");\n }\n\n // Build the request payload\n const payload: Record<string, unknown> = {\n sdkVersion,\n };\n\n // Straggler linking: if dev key is set AND anonKey is provided\n if (config.apiKey && anonKey) {\n payload.anonKey = anonKey;\n }\n\n if (config.environment) {\n payload.environment = config.environment;\n }\n if (importGraph) {\n payload.importGraph = importGraph;\n }\n if (healthReport) {\n payload.healthReport = healthReport;\n }\n if (diagnostics) {\n payload.diagnostics = diagnostics;\n }\n\n const url = `${config.endpoint}/v1/sdk/init`;\n\n const transport = transportOverride ?? httpsPostJson;\n let result;\n try {\n result = await transport(url, payload, {\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${effectiveKey}`,\n },\n timeoutMs: INIT_TIMEOUT_MS,\n signal,\n });\n } catch (err) {\n if (err instanceof HttpsStatusError) {\n const error = new Error(`Init request failed with status ${err.status}`);\n (error as unknown as Record<string, unknown>).status = err.status;\n throw error;\n }\n if (err instanceof HttpsBodyParseError) {\n // Preserve SyntaxError name so callers can distinguish parse failures\n // (existing test contract uses `name === \"SyntaxError\"`).\n const cause = err.cause;\n if (cause instanceof SyntaxError) throw cause;\n throw err;\n }\n if (err instanceof HttpsTransportError) {\n // Transport error — surface as-is; callers classify via message/name.\n throw err;\n }\n throw err;\n }\n\n return SdkInitResponseSchema.parse(result.body);\n}\n\n/**\n * Result returned by {@link performInit} when the backend reports an\n * account claim transition. `null` means no claim was present.\n */\nexport interface InitClaimResult {\n claimResult: NonNullable<SdkInitResponse[\"claimResult\"]>;\n}\n\n/**\n * Result of {@link writeClaimedKey}. The discriminator tells the\n * caller which on-disk source the key now lives at — and, if both\n * file writes failed, that no refresh of dependent state should be\n * attempted because there is no on-disk credential to back it.\n */\nexport interface WriteClaimedKeyResult {\n persisted: \"env-local\" | \"claimed-key\" | \"none\";\n}\n\n/**\n * Writes a claimed API key to disk using a fallback chain:\n * 1. `.env.local` — update or create with the new key\n * 2. `.glasstrace/claimed-key` — fallback if `.env.local` is not writable\n * 3. Dashboard message — if all file writes fail (key is never logged)\n *\n * The key value MUST NOT appear in any log output or stderr message.\n * In non-Node environments where `node:fs` is unavailable, falls through\n * directly to the dashboard message (step 3).\n *\n * Returns a {@link WriteClaimedKeyResult} so the caller can gate\n * downstream actions (specifically: managed MCP config refresh) on\n * the key actually having reached disk. Returning `persisted: \"none\"`\n * means the SDK could not write the key anywhere; refreshing\n * `.glasstrace/mcp.json` from the new key would put it out of sync\n * with the credential the runtime can actually read on the next\n * cold start.\n */\nexport async function writeClaimedKey(\n newApiKey: string,\n projectRoot?: string,\n): Promise<WriteClaimedKeyResult> {\n const modules = await loadFsPathAsync();\n\n if (modules) {\n const root = projectRoot ?? process.cwd();\n const envLocalPath = modules.path.join(root, \".env.local\");\n\n // Step 1: Try writing to .env.local\n let envLocalWritten = false;\n try {\n let content: string;\n try {\n content = await modules.fs.readFile(envLocalPath, \"utf-8\");\n // Replace all existing GLASSTRACE_API_KEY lines or append\n if (/^GLASSTRACE_API_KEY=.*/m.test(content)) {\n content = content.replace(\n /^GLASSTRACE_API_KEY=.*$/gm,\n `GLASSTRACE_API_KEY=${newApiKey}`,\n );\n } else {\n // Ensure trailing newline before appending\n if (content.length > 0 && !content.endsWith(\"\\n\")) {\n content += \"\\n\";\n }\n content += `GLASSTRACE_API_KEY=${newApiKey}\\n`;\n }\n } catch (readErr: unknown) {\n // Only create a new file when the file genuinely does not exist.\n // Other read errors (e.g., permission denied) should not silently\n // overwrite an existing .env.local that we cannot read.\n const code = readErr instanceof Error ? (readErr as NodeJS.ErrnoException).code : undefined;\n if (code !== \"ENOENT\") {\n throw readErr;\n }\n content = `GLASSTRACE_API_KEY=${newApiKey}\\n`;\n }\n\n await modules.fs.writeFile(envLocalPath, content, { encoding: \"utf-8\", mode: 0o600 });\n await modules.fs.chmod(envLocalPath, 0o600);\n envLocalWritten = true;\n } catch {\n // .env.local write failed — fall through to step 2\n }\n\n if (envLocalWritten) {\n try {\n process.stderr.write(\n \"[glasstrace] Account claimed! API key written to .env.local. Restart your dev server to use it.\\n\",\n );\n } catch { /* stderr is best-effort */ }\n return { persisted: \"env-local\" };\n }\n\n // Step 2: Try writing to .glasstrace/claimed-key\n let claimedKeyWritten = false;\n try {\n const dirPath = modules.path.join(root, GLASSTRACE_DIR);\n await modules.fs.mkdir(dirPath, { recursive: true, mode: 0o700 });\n await modules.fs.chmod(dirPath, 0o700);\n const claimedKeyPath = modules.path.join(dirPath, \"claimed-key\");\n await modules.fs.writeFile(claimedKeyPath, newApiKey, {\n encoding: \"utf-8\",\n mode: 0o600,\n });\n await modules.fs.chmod(claimedKeyPath, 0o600);\n claimedKeyWritten = true;\n } catch {\n // .glasstrace write also failed — fall through to step 3\n }\n\n if (claimedKeyWritten) {\n try {\n process.stderr.write(\n \"[glasstrace] Account claimed! API key written to .glasstrace/claimed-key. Copy it to your .env.local file.\\n\",\n );\n } catch { /* stderr is best-effort */ }\n return { persisted: \"claimed-key\" };\n }\n }\n\n // Step 3: All file writes failed (or node:fs unavailable) — log a message WITHOUT the key\n try {\n process.stderr.write(\n \"[glasstrace] Account claimed but could not write key to disk. Visit your dashboard settings to rotate and retrieve a new API key.\\n\",\n );\n } catch { /* stderr is best-effort */ }\n return { persisted: \"none\" };\n}\n\n/**\n * Orchestrates the full init flow: send request, update config, cache result.\n * This function MUST NOT throw.\n *\n * Returns the claim result when the backend reports an account claim\n * transition, or `null` when no claim result is available (including\n * when init is skipped due to rate-limit backoff, missing API key,\n * or request failure). Callers that do not need claim information\n * can safely ignore the return value.\n */\nexport async function performInit(\n config: ResolvedConfig,\n anonKey: AnonApiKey | null,\n sdkVersion: string,\n healthReport?: SdkHealthReport | null,\n): Promise<InitClaimResult | null> {\n lastInitSucceeded = false;\n\n // Skip if in rate-limit backoff\n if (rateLimitBackoff) {\n rateLimitBackoff = false; // Reset for next call\n return null;\n }\n\n // Guard flag: prevents recordInitFailure() from being called twice if the\n // inner catch body itself throws (e.g., an unexpected error in console.warn\n // or the instanceof checks). Without this flag, the outer safety-net catch\n // would call recordInitFailure() a second time, inflating initFailures in\n // the health report. Fix for DISC-1121.\n let failureRecorded = false;\n\n try {\n const effectiveKey = config.apiKey || anonKey;\n if (!effectiveKey) {\n console.warn(\"[glasstrace] No API key available for init request.\");\n return null;\n }\n\n // No outer AbortController timeout: `httpsPostJson` enforces a\n // per-attempt timeout (INIT_TIMEOUT_MS = 10s) AND a 20s total\n // deadline across retries. An outer 10s abort would race the first\n // attempt's own timeout and prevent the backoff-retry window from\n // ever running, defeating the transport's retry behavior.\n try {\n // Delegate to sendInitRequest to avoid duplicating fetch logic\n const result = await sendInitRequest(\n config,\n anonKey,\n sdkVersion,\n undefined,\n healthReport ?? undefined,\n undefined,\n );\n\n // Update in-memory config\n currentConfig = result;\n recordConfigSync(Date.now());\n if (healthReport) {\n acknowledgeHealthReport(healthReport);\n }\n lastInitSucceeded = true;\n\n // Persist to disk\n await saveCachedConfig(result);\n\n // Handle account claim transition — write key to disk, never to stderr\n if (result.claimResult) {\n let persisted: WriteClaimedKeyResult[\"persisted\"] = \"none\";\n try {\n const w = await writeClaimedKey(result.claimResult.newApiKey);\n persisted = w.persisted;\n } catch {\n // writeClaimedKey handles its own errors internally, but guard\n // against unexpected failures to ensure claimResult is never lost\n }\n\n // When the claimed key actually reached disk, refresh the\n // managed `.glasstrace/mcp.json` (if SDK-shaped) so MCP queries\n // start using the same credential ingestion now writes traces\n // with. Refresh failure must not lose claimResult — wrap the\n // whole thing in its own try/catch.\n if (persisted !== \"none\") {\n try {\n const resolved = await resolveEffectiveMcpCredential();\n await refreshGenericMcpConfigAtRuntime(\n process.cwd(),\n resolved.effective,\n resolved.anonKey,\n );\n } catch {\n // Refresh failure leaves the existing managed config in\n // place. The next CLI-driven `glasstrace mcp add` run will\n // detect the marker mismatch and prompt a re-run.\n }\n }\n\n return { claimResult: result.claimResult };\n }\n\n return null;\n } catch (err) {\n recordInitFailure();\n failureRecorded = true;\n\n // HttpsTransportError covers DNS/TCP/TLS/timeout from the\n // node:https transport itself — `httpsPostJson` raises timeouts\n // via this error class when its internal deadlines expire.\n if (err instanceof HttpsTransportError) {\n if (/timed out|aborted/i.test(err.message)) {\n console.warn(\"[glasstrace] ingestion_unreachable: Init request timed out.\");\n } else {\n console.warn(`[glasstrace] ingestion_unreachable: ${err.message}`);\n }\n return null;\n }\n\n // Check for HTTP status errors attached by sendInitRequest\n const status = (err as Record<string, unknown>).status;\n if (status === 401) {\n console.warn(\n \"[glasstrace] ingestion_auth_failed: Check your GLASSTRACE_API_KEY.\",\n );\n return null;\n }\n\n if (status === 429) {\n console.warn(\"[glasstrace] ingestion_rate_limited: Backing off.\");\n rateLimitBackoff = true;\n return null;\n }\n\n if (typeof status === \"number\" && status >= 400) {\n console.warn(\n `[glasstrace] Init request failed with status ${status}. Using cached config.`,\n );\n return null;\n }\n\n // Schema validation failure from sendInitRequest.parse\n // NOTE: Health report was already sent to the backend (HTTP 200).\n // Not acknowledging here means the next report will double-count\n // these values. This is intentional — over-reporting is preferable\n // to data loss when the response is unparseable (DISC-1120).\n if (err instanceof Error && err.name === \"ZodError\") {\n console.warn(\n \"[glasstrace] Init response failed validation (schema version mismatch?). Using cached config.\",\n );\n return null;\n }\n\n // Network error or other fetch failure\n console.warn(\n `[glasstrace] ingestion_unreachable: ${err instanceof Error ? err.message : String(err)}`,\n );\n return null;\n }\n } catch (err) {\n // Outermost catch — safety net for unexpected throws from the inner catch\n // body itself (e.g., an error in console.warn or instanceof checks).\n // Only record the failure if the inner catch did not already do so (DISC-1121).\n if (!failureRecorded) {\n recordInitFailure();\n }\n // Guard console.warn itself: performInit MUST NOT throw. If console.warn\n // throws here (the same failure mode this catch was added to handle), swallow\n // silently rather than violating the \"never throws\" contract.\n try {\n console.warn(\n `[glasstrace] Unexpected init error: ${err instanceof Error ? err.message : String(err)}`,\n );\n } catch { /* best-effort logging; never propagate */ }\n }\n\n return null;\n}\n\n/**\n * Returns the current capture config from the three-tier fallback chain:\n * 1. In-memory config from latest init response\n * 2. File cache (read at most once per process lifetime)\n * 3. DEFAULT_CAPTURE_CONFIG\n *\n * The disk read is cached via `configCacheChecked` to avoid repeated\n * synchronous I/O on the hot path (called by GlasstraceExporter on\n * every span export batch).\n */\nexport function getActiveConfig(): CaptureConfig {\n // Tier 1: in-memory\n if (currentConfig) {\n return currentConfig.config;\n }\n\n // Tier 2: file cache (only attempt once)\n if (!configCacheChecked) {\n configCacheChecked = true;\n const cached = loadCachedConfig();\n if (cached) {\n currentConfig = cached;\n return cached.config;\n }\n }\n\n // Tier 3: defaults\n return { ...DEFAULT_CAPTURE_CONFIG };\n}\n\n/**\n * Returns the `linkedAccountId` from the current in-memory init response,\n * or `undefined` if no init response is available or no account is linked.\n *\n * Used by the discovery endpoint to determine whether `claimed: true`\n * should be included in the response.\n */\nexport function getLinkedAccountId(): string | undefined {\n return currentConfig?.linkedAccountId;\n}\n\n/**\n * Returns the `claimResult` from the current in-memory init response,\n * or `undefined` if no init response is available or no claim occurred.\n *\n * Used by the discovery endpoint to detect in-flight claims: a valid\n * init response can include `claimResult` (claim happening NOW) without\n * `linkedAccountId` being set yet.\n */\nexport function getClaimResult(): SdkInitResponse[\"claimResult\"] {\n return currentConfig?.claimResult;\n}\n\n/**\n * Resets the in-memory config store. For testing only.\n */\nexport function _resetConfigForTesting(): void {\n currentConfig = null;\n configCacheChecked = false;\n rateLimitBackoff = false;\n lastInitSucceeded = false;\n transportOverride = null;\n}\n\n/**\n * Installs a test-only transport that replaces the `node:https` path\n * used by `sendInitRequest` and `performInit`. Tests use this to avoid\n * opening real sockets and to assert the SDK never routes through\n * `globalThis.fetch`. Pass `null` to restore the default transport.\n *\n * @internal Test-only. Never called from production code paths.\n */\nexport function _setTransportForTesting(fn: HttpsPostJsonFn | null): void {\n transportOverride = fn;\n}\n\n/**\n * Sets the in-memory config directly. Used by performInit and the orchestrator.\n */\nexport function _setCurrentConfig(config: SdkInitResponse): void {\n currentConfig = config;\n}\n\n/**\n * Returns whether rate-limit backoff is active. For testing only.\n */\nexport function _isRateLimitBackoff(): boolean {\n return rateLimitBackoff;\n}\n\n/**\n * Reads and clears the rate-limit backoff flag.\n * Called by the heartbeat after performInit returns null to detect 429 responses.\n * Returns true if a 429 occurred, false otherwise.\n */\nexport function consumeRateLimitFlag(): boolean {\n if (rateLimitBackoff) {\n rateLimitBackoff = false;\n return true;\n }\n return false;\n}\n\n/**\n * Returns true if the most recent performInit call completed the success path\n * (recordConfigSync + acknowledgeHealthReport were called).\n * Used by backgroundInit to decide whether to start the heartbeat.\n */\nexport function didLastInitSucceed(): boolean {\n return lastInitSucceeded;\n}\n\n/**\n * Result of {@link verifyInitReachable}.\n *\n * - `ok: true` — server acknowledged the init call with a valid, schema-\n * compliant payload. The anon key (if any) is registered server-side.\n * - `ok: false` with `reason: \"transport\"` — DNS/TCP/TLS/timeout failure.\n * No response reached the server (or couldn't be parsed off the wire).\n * `detail` is the raw cause (e.g. \"ECONNREFUSED\") with any leading\n * `fetch failed: ` prefix stripped; callers that render to the user\n * should add the prefix themselves to avoid doubling it.\n * - `ok: false` with `reason: \"rejected\"` — HTTP 4xx/5xx status. The\n * server received the call but declined it. `status` is set.\n * - `ok: false` with `reason: \"malformed\"` — HTTP 2xx but the body was\n * not valid JSON or did not match the protocol schema.\n */\nexport type VerifyInitResult =\n | { ok: true; response: SdkInitResponse }\n | { ok: false; reason: \"transport\"; detail: string }\n | { ok: false; reason: \"rejected\"; status: number; detail: string }\n | { ok: false; reason: \"malformed\"; detail: string };\n\n/**\n * Synchronously verifies that `/v1/sdk/init` is reachable and that the\n * provided anon key (if any) is registered server-side. Unlike\n * {@link performInit}, this function does NOT swallow errors — it\n * classifies them into the three user-actionable categories and\n * returns them.\n *\n * Used by the CLI `init` command to fail loudly when the init request\n * fails (DISC-493 Issue 3, DISC-494), rather than relying on the\n * runtime fire-and-forget call which can silently fail inside a\n * Next.js 16 process.\n *\n * The anon key is NEVER logged by this function. Error `detail`\n * strings are sanitized to the failure class only — the key does not\n * appear in transport, rejection, or malformed messages.\n */\nexport async function verifyInitReachable(\n config: ResolvedConfig,\n anonKey: AnonApiKey | null,\n sdkVersion: string,\n): Promise<VerifyInitResult> {\n try {\n const response = await sendInitRequest(config, anonKey, sdkVersion);\n return { ok: true, response };\n } catch (err) {\n // HTTP status error — server rejected the key.\n const status = (err as Record<string, unknown>).status;\n if (typeof status === \"number\") {\n return {\n ok: false,\n reason: \"rejected\",\n status,\n detail: `server returned HTTP ${status}`,\n };\n }\n\n // Schema validation failure (ZodError) or JSON parse error\n // (SyntaxError). Both mean the server responded but the body is\n // not a shape we can use.\n if (err instanceof Error && (err.name === \"ZodError\" || err.name === \"SyntaxError\")) {\n return {\n ok: false,\n reason: \"malformed\",\n detail: \"server returned malformed response\",\n };\n }\n\n // Everything else (transport errors, timeouts, abort, unknown) is\n // classified as transport. `detail` is the raw cause without a\n // `fetch failed:` prefix so the CLI (the only caller that renders\n // this) can format it as `fetch failed: <detail>` without risking\n // the double-prefix that would occur when the underlying error\n // already starts with `fetch failed:` (e.g., `HttpsTransportError`\n // from `sendSingleRequest`).\n const rawMessage = err instanceof Error ? err.message : String(err);\n const detail = rawMessage.startsWith(\"fetch failed: \")\n ? rawMessage.slice(\"fetch failed: \".length)\n : rawMessage;\n return { ok: false, reason: \"transport\", detail };\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/health-collector.ts","../src/https-transport.ts","../src/init-client.ts"],"sourcesContent":["import type { SdkHealthReport } from \"@glasstrace/protocol\";\n\n// --- Module-level state (singleton pattern matching init-client.ts) ---\n\n/** Spans successfully forwarded to the delegate exporter since last collect. */\nlet tracesExported = 0;\n\n/** Spans dropped: buffer overflow evictions + spans lost on shutdown. */\nlet tracesDropped = 0;\n\n/** Failed performInit attempts since last collect. */\nlet initFailures = 0;\n\n/** Timestamp (ms) of the last successful config sync (performInit success or cached config load). */\nlet lastConfigSyncAt: number | null = null;\n\n// --- Recording functions (called by other modules) ---\n\n/**\n * Records that spans were submitted to the delegate exporter.\n * Counts submission, not confirmed delivery (DISC-1118).\n * Called by GlasstraceExporter after delegate.export() is invoked.\n */\nexport function recordSpansExported(count: number): void {\n if (!Number.isFinite(count) || count < 0 || !Number.isInteger(count)) return;\n tracesExported += count;\n}\n\n/**\n * Records that spans were dropped (buffer overflow eviction or shutdown loss).\n * Called by GlasstraceExporter on buffer eviction and unresolved-key shutdown.\n */\nexport function recordSpansDropped(count: number): void {\n if (!Number.isFinite(count) || count < 0 || !Number.isInteger(count)) return;\n tracesDropped += count;\n}\n\n/**\n * Records a failed performInit attempt.\n * Called by performInit when the init request fails for any reason.\n */\nexport function recordInitFailure(): void {\n try { initFailures += 1; } catch { /* best-effort */ }\n}\n\n/**\n * Records the timestamp of a successful config sync.\n * Called by performInit on success and by loadCachedConfig when loading a valid cache.\n */\nexport function recordConfigSync(timestamp: number): void {\n try { lastConfigSyncAt = timestamp; } catch { /* best-effort */ }\n}\n\n// --- Collection ---\n\n/**\n * Snapshots the current health metrics into an SdkHealthReport without\n * resetting counters. Counters are only reset when {@link acknowledgeHealthReport}\n * is called after the init request succeeds. This two-phase approach prevents\n * metric loss when `performInit` fails — the counters persist for the next\n * init attempt.\n *\n * On the first init call, all counters will be zero, which is correct.\n *\n * @param sdkVersion - The SDK version string to include in the report.\n * @returns The health report, or null if collection fails unexpectedly.\n */\nexport function collectHealthReport(sdkVersion: string): SdkHealthReport | null {\n try {\n const now = Date.now();\n const configAge = lastConfigSyncAt !== null ? Math.max(0, now - lastConfigSyncAt) : 0;\n\n return {\n tracesExportedSinceLastInit: tracesExported,\n tracesDropped,\n initFailures,\n configAge: Math.round(configAge),\n sdkVersion,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Subtracts the reported values from the running counters after a health\n * report has been successfully delivered to the backend. Called by\n * `performInit` on the success path. If init fails, counters persist\n * for the next attempt.\n *\n * Uses subtraction instead of zeroing to preserve any increments that\n * occurred between the snapshot (`collectHealthReport`) and delivery\n * (e.g., spans exported during the init HTTP call). Values are clamped\n * to 0 to guard against edge cases.\n *\n * Core invariant (DISC-1123): for any finite counter C and finite reported\n * value, after acknowledge:\n * C_new = max(0, C_before_ack - reported_value)\n * This guarantees:\n * 1. Reported finite, non-negative values are removed exactly once\n * (no double-counting).\n * 2. Activity between snapshot and acknowledge is preserved (not lost).\n * 3. The counter never goes negative (clamp prevents underflow).\n * Corruption vectors guarded: non-finite report fields (NaN/±Infinity)\n * preserve the counter; negative finite report fields are clamped to 0\n * before subtraction.\n *\n * `lastConfigSyncAt` is NOT affected — config age measures time since\n * the last successful sync, not the last acknowledgment.\n */\nexport function acknowledgeHealthReport(report: SdkHealthReport): void {\n const exp = Math.max(0, report.tracesExportedSinceLastInit);\n const expVal = tracesExported - exp;\n tracesExported = Number.isFinite(expVal) ? Math.max(0, expVal) : tracesExported;\n\n const drop = Math.max(0, report.tracesDropped);\n const dropVal = tracesDropped - drop;\n tracesDropped = Number.isFinite(dropVal) ? Math.max(0, dropVal) : tracesDropped;\n\n const fail = Math.max(0, report.initFailures);\n const failVal = initFailures - fail;\n initFailures = Number.isFinite(failVal) ? Math.max(0, failVal) : initFailures;\n}\n\n// --- Test support ---\n\n/**\n * Resets all health metrics to initial state. For testing only.\n */\nexport function _resetHealthForTesting(): void {\n tracesExported = 0;\n tracesDropped = 0;\n initFailures = 0;\n lastConfigSyncAt = null;\n}\n","/**\n * Minimal Node-native HTTPS transport used by the SDK init call.\n *\n * ## Why this exists\n *\n * Next.js 16 patches the global `fetch` to add caching, revalidation,\n * and request deduplication. When the SDK is bundled into a Next.js\n * process (instrumentation.ts path), outbound calls to\n * `api.glasstrace.dev` get intercepted by the patched fetch and can\n * silently hang — the fetch promise never resolves (DISC-493 Issue 3).\n *\n * A silent init hang is catastrophic: the SDK stays in \"pending key\"\n * state forever, enriched spans are buffered without ever being\n * exported, and anonymous keys are never registered server-side\n * (DISC-494).\n *\n * ## Why `node:https`\n *\n * `node:https` is a Node.js core module. It has zero bundle weight\n * (important because the SDK is tsup-inlined into every consumer's\n * bundle) and is always available on Node.js >= 20. Using it directly\n * bypasses the global `fetch` patching entirely — Next.js never sees\n * the request.\n *\n * Alternatives considered and rejected:\n *\n * - **`undici` as a runtime dep** — adds ~400KB inlined into every\n * consumer bundle.\n * - **`fetch(..., { cache: \"no-store\", next: { revalidate: 0 } })`** —\n * bandaid. Couples the SDK to Next.js's fetch-extension API and still\n * relies on Next's patched fetch behaving correctly. Explicitly\n * forbidden by the task brief for this reason.\n * - **Monkey-patch `globalThis.fetch`** — forbidden in the public SDK\n * (`glasstrace-sdk/CLAUDE.md`). Bypassing the patched fetch by\n * calling a different API is avoidance, not patching.\n *\n * ## Structure\n *\n * `httpsPostJson` is the only exported function. It:\n * - Sends a POST to a URL with a JSON body\n * - Applies a per-request timeout (default 10s)\n * - Retries transport-level failures (DNS, TCP, TLS) with backoff\n * - Never retries HTTP status errors — those are surfaced immediately\n * - Distinguishes transport failure, server error, and body-parse error\n * so callers can render actionable messages\n */\nimport {\n request as httpsRequest,\n type RequestOptions as HttpsRequestOptions,\n} from \"node:https\";\nimport {\n request as httpRequest,\n type IncomingMessage,\n} from \"node:http\";\nimport { URL } from \"node:url\";\n\n/** Error thrown when the HTTP request never completed (DNS/TCP/TLS/timeout). */\nexport class HttpsTransportError extends Error {\n readonly kind = \"transport\" as const;\n readonly cause?: unknown;\n constructor(message: string, cause?: unknown) {\n super(message);\n this.name = \"HttpsTransportError\";\n this.cause = cause;\n }\n}\n\n/** Error thrown when the server returned a non-2xx HTTP status. */\nexport class HttpsStatusError extends Error {\n readonly kind = \"status\" as const;\n readonly status: number;\n /** Raw response body text (may be truncated by caller if large). */\n readonly body: string;\n constructor(status: number, body: string) {\n super(`Server returned HTTP ${status}`);\n this.name = \"HttpsStatusError\";\n this.status = status;\n this.body = body;\n }\n}\n\n/** Error thrown when the response body was not parseable JSON. */\nexport class HttpsBodyParseError extends Error {\n readonly kind = \"parse\" as const;\n readonly status: number;\n readonly cause?: unknown;\n constructor(status: number, cause?: unknown) {\n super(`Server returned malformed response (HTTP ${status})`);\n this.name = \"HttpsBodyParseError\";\n this.status = status;\n this.cause = cause;\n }\n}\n\n/** Options controlling timeout and retry behavior. */\nexport interface HttpsPostJsonOptions {\n /** Parsed headers (including Content-Type, Authorization, etc). */\n headers: Record<string, string>;\n /** Per-attempt timeout, ms. Defaults to 10000. */\n timeoutMs?: number;\n /**\n * Total number of attempts INCLUDING the first. Defaults to 3\n * (initial + 2 retries). Only transport errors are retried.\n */\n maxAttempts?: number;\n /**\n * Backoff delays between retries, ms. The array length should be\n * `maxAttempts - 1`. Defaults to [500, 1500].\n */\n retryDelaysMs?: readonly number[];\n /**\n * Total deadline across all attempts, ms. Defaults to 20000.\n * If exceeded, no further retries are attempted and the last error\n * is surfaced. Guards against CLI hang on flaky networks.\n */\n totalDeadlineMs?: number;\n /**\n * Abort signal. When aborted, the in-flight request is terminated and\n * no further retries are attempted.\n */\n signal?: AbortSignal;\n /**\n * Scheduler injection point for tests. Defaults to `setTimeout`.\n * Using fake timers in tests requires the injected scheduler to honor\n * `vi.advanceTimersByTime()` — Node's real setTimeout is fine when no\n * fake timers are installed.\n */\n scheduler?: (fn: () => void, ms: number) => { unref?: () => void };\n /**\n * Alternate HTTPS request function. Injected by tests to simulate\n * Next.js-style fetch patching (assert call count stays zero) or to\n * mock transport behavior without opening real sockets.\n */\n requestImpl?: typeof httpsRequest;\n /**\n * Alternate HTTP request function, used when the URL is `http://`.\n * Splitting http/https lets tests use a local non-TLS mock server.\n */\n httpRequestImpl?: typeof httpRequest;\n}\n\n/** Shape of a successful response. */\nexport interface HttpsPostJsonResult {\n /** HTTP status code. Always in [200, 299] for success. */\n status: number;\n /** Parsed JSON body. May be `undefined` for 204 No Content. */\n body: unknown;\n /** Raw body text for diagnostics. */\n raw: string;\n}\n\n/** Delays so a failing test still completes before the suite's timeout. */\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_RETRY_DELAYS_MS = [500, 1500] as const;\nconst DEFAULT_TOTAL_DEADLINE_MS = 20_000;\n\n/**\n * Sends a POST request with a JSON body using `node:https`. Bypasses\n * any `globalThis.fetch` patching (Next.js 16, MSW, etc).\n *\n * @throws {HttpsTransportError} DNS failure, TCP reset, TLS handshake\n * failure, request timeout, or abort.\n * @throws {HttpsStatusError} HTTP response with status >= 400.\n * @throws {HttpsBodyParseError} HTTP 2xx with non-JSON body (status not\n * equal to 204).\n */\nexport async function httpsPostJson(\n url: string,\n jsonBody: unknown,\n options: HttpsPostJsonOptions,\n): Promise<HttpsPostJsonResult> {\n const parsed = new URL(url);\n const isHttps = parsed.protocol === \"https:\";\n const isHttp = parsed.protocol === \"http:\";\n if (!isHttps && !isHttp) {\n throw new HttpsTransportError(\n `Unsupported protocol: ${parsed.protocol} (expected http: or https:)`,\n );\n }\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const maxAttempts = options.maxAttempts ?? 3;\n const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;\n const totalDeadlineMs = options.totalDeadlineMs ?? DEFAULT_TOTAL_DEADLINE_MS;\n const scheduler = options.scheduler ?? ((fn, ms) => setTimeout(fn, ms));\n const requestImpl = isHttps\n ? (options.requestImpl ?? httpsRequest)\n : (options.httpRequestImpl ?? httpRequest);\n\n // Serialize once so retries use the exact same bytes.\n let payload: string;\n try {\n payload = JSON.stringify(jsonBody);\n } catch (err) {\n throw new HttpsTransportError(\n `Failed to serialize request body: ${err instanceof Error ? err.message : String(err)}`,\n err,\n );\n }\n const payloadBuffer = Buffer.from(payload, \"utf-8\");\n\n const startedAt = Date.now();\n let lastError: unknown;\n\n for (let attempt = 0; attempt < maxAttempts; attempt += 1) {\n if (options.signal?.aborted) {\n throw new HttpsTransportError(\"Request aborted\");\n }\n // Respect the total deadline — don't start another attempt if we'd\n // blow past it even before issuing the request.\n const elapsed = Date.now() - startedAt;\n if (elapsed >= totalDeadlineMs) {\n break;\n }\n const remainingBudget = totalDeadlineMs - elapsed;\n const attemptTimeoutMs = Math.min(timeoutMs, remainingBudget);\n\n try {\n return await sendSingleRequest(\n parsed,\n payloadBuffer,\n options.headers,\n attemptTimeoutMs,\n options.signal,\n requestImpl,\n );\n } catch (err) {\n lastError = err;\n // Never retry status/parse errors — they're server responses, not\n // transient network failures.\n if (err instanceof HttpsStatusError || err instanceof HttpsBodyParseError) {\n throw err;\n }\n const isLast = attempt === maxAttempts - 1;\n if (isLast) break;\n\n const delayMs = retryDelaysMs[attempt] ?? retryDelaysMs[retryDelaysMs.length - 1] ?? 0;\n const elapsedBeforeSleep = Date.now() - startedAt;\n const remaining = totalDeadlineMs - elapsedBeforeSleep;\n if (remaining <= 0) break;\n const actualDelayMs = Math.min(delayMs, remaining);\n await sleep(actualDelayMs, scheduler, options.signal);\n }\n }\n\n if (lastError instanceof HttpsTransportError) throw lastError;\n throw new HttpsTransportError(\n lastError instanceof Error ? lastError.message : \"Request failed\",\n lastError,\n );\n}\n\n/**\n * Fires a single HTTPS request. Resolves with the parsed result on 2xx,\n * throws the appropriate typed error otherwise. Caller handles retries.\n */\nfunction sendSingleRequest(\n url: URL,\n payload: Buffer,\n headers: Record<string, string>,\n timeoutMs: number,\n signal: AbortSignal | undefined,\n requestImpl: typeof httpsRequest,\n): Promise<HttpsPostJsonResult> {\n return new Promise<HttpsPostJsonResult>((resolve, reject) => {\n // Merge caller headers with Content-Length so Node doesn't chunk\n // the body. Explicit content-length also prevents confusion from\n // servers that reject chunked POSTs.\n const finalHeaders: Record<string, string | number> = {\n ...headers,\n \"Content-Length\": payload.byteLength,\n };\n\n const reqOptions: HttpsRequestOptions = {\n method: \"POST\",\n hostname: url.hostname,\n port: url.port === \"\" ? undefined : Number(url.port),\n path: `${url.pathname}${url.search}`,\n headers: finalHeaders,\n // Explicit timeout at the socket level. Still complemented by a\n // manual timer below because `timeout` only fires when the socket\n // is idle — it does not cover \"TLS handshake hangs forever\".\n timeout: timeoutMs,\n };\n\n let settled = false;\n // Hoisted so every settle path can clear the manual timer and drop\n // the optional abort listener. Assigned below once `req`, `timer`,\n // and `onAbort` exist.\n let cleanup = (): void => {};\n const settle = (fn: () => void): void => {\n if (settled) return;\n settled = true;\n cleanup();\n fn();\n };\n\n const req = requestImpl(reqOptions, (res: IncomingMessage) => {\n const chunks: Buffer[] = [];\n res.on(\"data\", (chunk: Buffer | string) => {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk, \"utf-8\") : chunk);\n });\n res.on(\"end\", () => {\n const raw = Buffer.concat(chunks).toString(\"utf-8\");\n const status = res.statusCode ?? 0;\n if (status < 200 || status >= 300) {\n settle(() => reject(new HttpsStatusError(status, raw)));\n return;\n }\n // HTTP 204: no body is expected; resolve with undefined.\n if (status === 204 || raw.length === 0) {\n settle(() => resolve({ status, body: undefined, raw }));\n return;\n }\n try {\n const parsed = JSON.parse(raw);\n settle(() => resolve({ status, body: parsed, raw }));\n } catch (err) {\n settle(() => reject(new HttpsBodyParseError(status, err)));\n }\n });\n res.on(\"error\", (err) => {\n settle(() => reject(new HttpsTransportError(`Response stream error: ${err.message}`, err)));\n });\n });\n\n // A single manual timeout guards against handshake/DNS hangs that\n // the `timeout` option in `request()` does not cover. We destroy the\n // socket on timeout so the node:https layer doesn't keep it alive.\n const timer = setTimeout(() => {\n settle(() => {\n req.destroy(new Error(\"Request timed out\"));\n reject(new HttpsTransportError(`Request timed out after ${timeoutMs}ms`));\n });\n }, timeoutMs);\n // Don't block process exit while the timer is running.\n if (typeof timer.unref === \"function\") timer.unref();\n\n // Hoisted so `cleanup` can remove it on settle. Only registered\n // on the signal below when `signal !== undefined`.\n const onAbort = (): void => {\n settle(() => {\n req.destroy(new Error(\"Aborted\"));\n reject(new HttpsTransportError(\"Request aborted\"));\n });\n };\n\n // Install cleanup now that `timer` and `onAbort` exist. Invoked by\n // every settle path (success, status-error, parse-error, transport\n // error, timeout, abort) to clear the manual timer and drop the\n // abort listener so long-lived signals don't accumulate listeners.\n cleanup = (): void => {\n clearTimeout(timer);\n if (signal !== undefined) {\n signal.removeEventListener(\"abort\", onAbort);\n }\n };\n\n req.on(\"error\", (err) => {\n settle(() => reject(new HttpsTransportError(`fetch failed: ${err.message}`, err)));\n });\n\n req.on(\"timeout\", () => {\n settle(() => {\n req.destroy(new Error(\"Request timed out\"));\n reject(new HttpsTransportError(`Request timed out after ${timeoutMs}ms`));\n });\n });\n\n if (signal !== undefined) {\n if (signal.aborted) {\n req.destroy(new Error(\"Aborted\"));\n settle(() => reject(new HttpsTransportError(\"Request aborted\")));\n return;\n }\n signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n\n req.end(payload);\n });\n}\n\n/**\n * Delay helper that honors an AbortSignal. We cannot use `setTimeout`'s\n * built-in `signal` option because it is not available in older Node 20\n * patch releases (added in 20.6).\n *\n * Both settle paths (timer fires, signal aborts) clear the pending\n * timer and remove the abort listener so this helper remains leak-free\n * under heavy retry/abort usage.\n */\nfunction sleep(\n ms: number,\n scheduler: (fn: () => void, ms: number) => { unref?: () => void },\n signal: AbortSignal | undefined,\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n let settled = false;\n // Forward-declared so `settle` below can reference it. Assigned\n // once `timer` exists.\n let cleanup = (): void => {};\n const settle = (fn: () => void): void => {\n if (settled) return;\n settled = true;\n cleanup();\n fn();\n };\n const onAbort = (): void => {\n settle(() => reject(new HttpsTransportError(\"Request aborted\")));\n };\n const timer = scheduler(() => {\n settle(resolve);\n }, ms);\n if (typeof timer.unref === \"function\") timer.unref();\n cleanup = (): void => {\n // `scheduler` returns whatever `setTimeout` returns (NodeJS.Timeout\n // in Node, number in jsdom). Both are accepted by `clearTimeout`.\n clearTimeout(timer as unknown as NodeJS.Timeout);\n if (signal !== undefined) {\n signal.removeEventListener(\"abort\", onAbort);\n }\n };\n if (signal !== undefined) {\n if (signal.aborted) {\n onAbort();\n return;\n }\n signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n });\n}\n","import {\n SdkInitResponseSchema,\n SdkCachedConfigSchema,\n DEFAULT_CAPTURE_CONFIG,\n} from \"@glasstrace/protocol\";\nimport type {\n SdkInitResponse,\n CaptureConfig,\n AnonApiKey,\n ImportGraphPayload,\n SdkHealthReport,\n SdkDiagnosticCode,\n} from \"@glasstrace/protocol\";\nimport type { ResolvedConfig } from \"./env-detection.js\";\nimport { recordInitFailure, recordConfigSync, acknowledgeHealthReport } from \"./health-collector.js\";\nimport {\n httpsPostJson,\n HttpsStatusError,\n HttpsTransportError,\n HttpsBodyParseError,\n} from \"./https-transport.js\";\nimport {\n resolveEffectiveMcpCredential,\n refreshGenericMcpConfigAtRuntime,\n} from \"./mcp-runtime.js\";\nimport { atomicWriteFile } from \"./atomic-write.js\";\n\nconst GLASSTRACE_DIR = \".glasstrace\";\nconst CONFIG_FILE = \"config\";\nconst TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000;\nconst INIT_TIMEOUT_MS = 10_000;\n\n/**\n * Lazily imports `node:fs/promises` and `node:path`. Returns `null` if\n * the modules are unavailable (non-Node environments). Cached after first call.\n */\nlet fsPathAsyncCache: { fs: typeof import(\"node:fs/promises\"); path: typeof import(\"node:path\") } | null | undefined;\n\nasync function loadFsPathAsync(): Promise<{ fs: typeof import(\"node:fs/promises\"); path: typeof import(\"node:path\") } | null> {\n if (fsPathAsyncCache !== undefined) return fsPathAsyncCache;\n try {\n const [fs, path] = await Promise.all([\n import(\"node:fs/promises\"),\n import(\"node:path\"),\n ]);\n fsPathAsyncCache = { fs, path };\n return fsPathAsyncCache;\n } catch {\n fsPathAsyncCache = null;\n return null;\n }\n}\n\n/**\n * Lazily imports synchronous `node:fs` and `node:path` via `require()`.\n * Returns `null` when unavailable. Used by `loadCachedConfig` which is\n * synchronous for startup performance.\n */\nfunction loadFsSyncOrNull(): { readFileSync: typeof import(\"node:fs\").readFileSync; join: typeof import(\"node:path\").join } | null {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, glasstrace/no-unguarded-node-require -- guarded by the surrounding try/catch in `loadFsSyncOrNull`; throw returns `null`, callers (e.g. `loadCachedConfig`) treat it as no cached config (DISC-1555).\n const fs = require(\"node:fs\") as typeof import(\"node:fs\");\n // eslint-disable-next-line @typescript-eslint/no-require-imports, glasstrace/no-unguarded-node-require -- guarded by the same try/catch as the preceding `node:fs` require (DISC-1555).\n const path = require(\"node:path\") as typeof import(\"node:path\");\n return { readFileSync: fs.readFileSync, join: path.join };\n } catch {\n return null;\n }\n}\n\n/**\n * Test-only transport hook. When set, `sendInitRequest` calls this\n * instead of `httpsPostJson`. Enables unit tests to assert that the\n * SDK never routes through `globalThis.fetch` (Next.js patching) by\n * injecting a pure-function transport that never touches the network.\n *\n * Production code never sets this. Reset via `_resetConfigForTesting()`.\n */\ntype HttpsPostJsonFn = typeof httpsPostJson;\nlet transportOverride: HttpsPostJsonFn | null = null;\n\n/** In-memory config from the latest successful init response. */\nlet currentConfig: SdkInitResponse | null = null;\n\n/** Whether the disk cache has already been checked by getActiveConfig(). */\nlet configCacheChecked = false;\n\n/** Whether the next init call should be skipped (rate-limit backoff). */\nlet rateLimitBackoff = false;\n\n/** Whether the most recent performInit call completed the success path. */\nlet lastInitSucceeded = false;\n\n/**\n * Reads and validates a cached config file from `.glasstrace/config`.\n * Returns the parsed `SdkInitResponse` or `null` on any failure,\n * including when `node:fs` is unavailable (non-Node environments).\n */\nexport function loadCachedConfig(projectRoot?: string): SdkInitResponse | null {\n const modules = loadFsSyncOrNull();\n if (!modules) return null;\n\n const root = projectRoot ?? process.cwd();\n const configPath = modules.join(root, GLASSTRACE_DIR, CONFIG_FILE);\n\n try {\n // Use synchronous read for startup performance (this is called during init)\n const content = modules.readFileSync(configPath, \"utf-8\");\n const parsed = JSON.parse(content);\n const cached = SdkCachedConfigSchema.parse(parsed);\n\n // Warn if cache is stale\n const age = Date.now() - cached.cachedAt;\n if (age > TWENTY_FOUR_HOURS_MS) {\n console.warn(\n `[glasstrace] Cached config is ${Math.round(age / 3600000)}h old. Will refresh on next init.`,\n );\n }\n\n // Parse the response through the schema\n const result = SdkInitResponseSchema.safeParse(cached.response);\n if (result.success) {\n recordConfigSync(cached.cachedAt);\n return result.data;\n }\n\n console.warn(\"[glasstrace] Cached config failed validation. Using defaults.\");\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Persists the init response to `.glasstrace/config` using the SDK 2.0\n * atomic-write protocol (`tmp + fsync(tmp) + rename + fsync(parent)`).\n * Silently skipped when `node:fs` is unavailable (non-Node environments).\n * On I/O failure, logs a warning.\n *\n * Atomicity: the payload is written to `.glasstrace/config.tmp`, fsynced\n * to durable storage, then renamed into place; the parent directory is\n * fsynced last so the rename survives an immediate crash. `rename` is\n * atomic on POSIX filesystems, so readers either see the previous valid\n * config or the new valid config — never a truncated or partially-written\n * file (DISC-1247 Scenario 5). If any step fails, the temp file is\n * cleaned up on a best-effort basis.\n */\nexport async function saveCachedConfig(\n response: SdkInitResponse,\n projectRoot?: string,\n): Promise<void> {\n const modules = await loadFsPathAsync();\n if (!modules) return;\n\n const root = projectRoot ?? process.cwd();\n const dirPath = modules.path.join(root, GLASSTRACE_DIR);\n const configPath = modules.path.join(dirPath, CONFIG_FILE);\n\n try {\n await modules.fs.mkdir(dirPath, { recursive: true, mode: 0o700 });\n await modules.fs.chmod(dirPath, 0o700);\n const cached = {\n response,\n cachedAt: Date.now(),\n };\n // Atomic write per SDK 2.0 §4.3: tmp + fsync(tmp) + rename +\n // fsync(parent). Sibling temp guarantees same-filesystem rename\n // (atomic per POSIX). The helper best-effort cleans up the tmp\n // file on failure (DISC-1247 Scenario 5).\n await atomicWriteFile(configPath, JSON.stringify(cached), {\n encoding: \"utf-8\",\n mode: 0o600,\n });\n // chmod the final path to defend against platforms that don't honor\n // the mode passed to writeFile/rename on first creation.\n await modules.fs.chmod(configPath, 0o600);\n } catch (err) {\n console.warn(\n `[glasstrace] Failed to cache config to ${configPath}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\n/**\n * Sends a POST request to `/v1/sdk/init`.\n * Validates the response against `SdkInitResponseSchema`.\n *\n * Uses `node:https` via {@link httpsPostJson} rather than the global\n * `fetch` because Next.js 16 patches `fetch` for caching/revalidation\n * and can cause the init request to silently hang (DISC-493 Issue 3).\n * Retries transport-level failures (DNS, TCP, TLS) twice with 500ms +\n * 1500ms backoff, capped at a 20-second total deadline. Server responses\n * (HTTP 4xx/5xx) are never retried and are surfaced immediately.\n */\nexport async function sendInitRequest(\n config: ResolvedConfig,\n anonKey: AnonApiKey | null,\n sdkVersion: string,\n importGraph?: ImportGraphPayload,\n healthReport?: SdkHealthReport,\n diagnostics?: Array<{ code: SdkDiagnosticCode; message: string; timestamp: number }>,\n signal?: AbortSignal,\n): Promise<SdkInitResponse> {\n // Determine the API key for auth. Use || (not ??) so empty strings\n // fall through to the anonymous key — defense in depth for DISC-467.\n const effectiveKey = config.apiKey || anonKey;\n if (!effectiveKey) {\n throw new Error(\"No API key available for init request\");\n }\n\n // Build the request payload\n const payload: Record<string, unknown> = {\n sdkVersion,\n };\n\n // Straggler linking: if dev key is set AND anonKey is provided\n if (config.apiKey && anonKey) {\n payload.anonKey = anonKey;\n }\n\n if (config.environment) {\n payload.environment = config.environment;\n }\n if (importGraph) {\n payload.importGraph = importGraph;\n }\n if (healthReport) {\n payload.healthReport = healthReport;\n }\n if (diagnostics) {\n payload.diagnostics = diagnostics;\n }\n\n const url = `${config.endpoint}/v1/sdk/init`;\n\n const transport = transportOverride ?? httpsPostJson;\n let result;\n try {\n result = await transport(url, payload, {\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${effectiveKey}`,\n },\n timeoutMs: INIT_TIMEOUT_MS,\n signal,\n });\n } catch (err) {\n if (err instanceof HttpsStatusError) {\n const error = new Error(`Init request failed with status ${err.status}`);\n (error as unknown as Record<string, unknown>).status = err.status;\n throw error;\n }\n if (err instanceof HttpsBodyParseError) {\n // Preserve SyntaxError name so callers can distinguish parse failures\n // (existing test contract uses `name === \"SyntaxError\"`).\n const cause = err.cause;\n if (cause instanceof SyntaxError) throw cause;\n throw err;\n }\n if (err instanceof HttpsTransportError) {\n // Transport error — surface as-is; callers classify via message/name.\n throw err;\n }\n throw err;\n }\n\n return SdkInitResponseSchema.parse(result.body);\n}\n\n/**\n * Result returned by {@link performInit} when the backend reports an\n * account claim transition. `null` means no claim was present.\n */\nexport interface InitClaimResult {\n claimResult: NonNullable<SdkInitResponse[\"claimResult\"]>;\n}\n\n/**\n * Result of {@link writeClaimedKey}. The discriminator tells the\n * caller which on-disk source the key now lives at — and, if both\n * file writes failed, that no refresh of dependent state should be\n * attempted because there is no on-disk credential to back it.\n */\nexport interface WriteClaimedKeyResult {\n persisted: \"env-local\" | \"claimed-key\" | \"none\";\n}\n\n/**\n * Writes a claimed API key to disk using a fallback chain:\n * 1. `.env.local` — update or create with the new key\n * 2. `.glasstrace/claimed-key` — fallback if `.env.local` is not writable\n * 3. Dashboard message — if all file writes fail (key is never logged)\n *\n * The key value MUST NOT appear in any log output or stderr message.\n * In non-Node environments where `node:fs` is unavailable, falls through\n * directly to the dashboard message (step 3).\n *\n * Returns a {@link WriteClaimedKeyResult} so the caller can gate\n * downstream actions (specifically: managed MCP config refresh) on\n * the key actually having reached disk. Returning `persisted: \"none\"`\n * means the SDK could not write the key anywhere; refreshing\n * `.glasstrace/mcp.json` from the new key would put it out of sync\n * with the credential the runtime can actually read on the next\n * cold start.\n */\nexport async function writeClaimedKey(\n newApiKey: string,\n projectRoot?: string,\n): Promise<WriteClaimedKeyResult> {\n const modules = await loadFsPathAsync();\n\n if (modules) {\n const root = projectRoot ?? process.cwd();\n const envLocalPath = modules.path.join(root, \".env.local\");\n\n // Step 1: Try writing to .env.local\n let envLocalWritten = false;\n try {\n let content: string;\n try {\n content = await modules.fs.readFile(envLocalPath, \"utf-8\");\n // Replace all existing GLASSTRACE_API_KEY lines or append\n if (/^GLASSTRACE_API_KEY=.*/m.test(content)) {\n content = content.replace(\n /^GLASSTRACE_API_KEY=.*$/gm,\n `GLASSTRACE_API_KEY=${newApiKey}`,\n );\n } else {\n // Ensure trailing newline before appending\n if (content.length > 0 && !content.endsWith(\"\\n\")) {\n content += \"\\n\";\n }\n content += `GLASSTRACE_API_KEY=${newApiKey}\\n`;\n }\n } catch (readErr: unknown) {\n // Only create a new file when the file genuinely does not exist.\n // Other read errors (e.g., permission denied) should not silently\n // overwrite an existing .env.local that we cannot read.\n const code = readErr instanceof Error ? (readErr as NodeJS.ErrnoException).code : undefined;\n if (code !== \"ENOENT\") {\n throw readErr;\n }\n content = `GLASSTRACE_API_KEY=${newApiKey}\\n`;\n }\n\n await modules.fs.writeFile(envLocalPath, content, { encoding: \"utf-8\", mode: 0o600 });\n await modules.fs.chmod(envLocalPath, 0o600);\n envLocalWritten = true;\n } catch {\n // .env.local write failed — fall through to step 2\n }\n\n if (envLocalWritten) {\n try {\n process.stderr.write(\n \"[glasstrace] Account claimed! API key written to .env.local. Restart your dev server to use it.\\n\",\n );\n } catch { /* stderr is best-effort */ }\n return { persisted: \"env-local\" };\n }\n\n // Step 2: Try writing to .glasstrace/claimed-key\n let claimedKeyWritten = false;\n try {\n const dirPath = modules.path.join(root, GLASSTRACE_DIR);\n await modules.fs.mkdir(dirPath, { recursive: true, mode: 0o700 });\n await modules.fs.chmod(dirPath, 0o700);\n const claimedKeyPath = modules.path.join(dirPath, \"claimed-key\");\n await modules.fs.writeFile(claimedKeyPath, newApiKey, {\n encoding: \"utf-8\",\n mode: 0o600,\n });\n await modules.fs.chmod(claimedKeyPath, 0o600);\n claimedKeyWritten = true;\n } catch {\n // .glasstrace write also failed — fall through to step 3\n }\n\n if (claimedKeyWritten) {\n try {\n process.stderr.write(\n \"[glasstrace] Account claimed! API key written to .glasstrace/claimed-key. Copy it to your .env.local file.\\n\",\n );\n } catch { /* stderr is best-effort */ }\n return { persisted: \"claimed-key\" };\n }\n }\n\n // Step 3: All file writes failed (or node:fs unavailable) — log a message WITHOUT the key\n try {\n process.stderr.write(\n \"[glasstrace] Account claimed but could not write key to disk. Visit your dashboard settings to rotate and retrieve a new API key.\\n\",\n );\n } catch { /* stderr is best-effort */ }\n return { persisted: \"none\" };\n}\n\n/**\n * Orchestrates the full init flow: send request, update config, cache result.\n * This function MUST NOT throw.\n *\n * Returns the claim result when the backend reports an account claim\n * transition, or `null` when no claim result is available (including\n * when init is skipped due to rate-limit backoff, missing API key,\n * or request failure). Callers that do not need claim information\n * can safely ignore the return value.\n */\nexport async function performInit(\n config: ResolvedConfig,\n anonKey: AnonApiKey | null,\n sdkVersion: string,\n healthReport?: SdkHealthReport | null,\n): Promise<InitClaimResult | null> {\n lastInitSucceeded = false;\n\n // Skip if in rate-limit backoff\n if (rateLimitBackoff) {\n rateLimitBackoff = false; // Reset for next call\n return null;\n }\n\n // Guard flag: prevents recordInitFailure() from being called twice if the\n // inner catch body itself throws (e.g., an unexpected error in console.warn\n // or the instanceof checks). Without this flag, the outer safety-net catch\n // would call recordInitFailure() a second time, inflating initFailures in\n // the health report. Fix for DISC-1121.\n let failureRecorded = false;\n\n try {\n const effectiveKey = config.apiKey || anonKey;\n if (!effectiveKey) {\n console.warn(\"[glasstrace] No API key available for init request.\");\n return null;\n }\n\n // No outer AbortController timeout: `httpsPostJson` enforces a\n // per-attempt timeout (INIT_TIMEOUT_MS = 10s) AND a 20s total\n // deadline across retries. An outer 10s abort would race the first\n // attempt's own timeout and prevent the backoff-retry window from\n // ever running, defeating the transport's retry behavior.\n try {\n // Delegate to sendInitRequest to avoid duplicating fetch logic\n const result = await sendInitRequest(\n config,\n anonKey,\n sdkVersion,\n undefined,\n healthReport ?? undefined,\n undefined,\n );\n\n // Update in-memory config\n currentConfig = result;\n recordConfigSync(Date.now());\n if (healthReport) {\n acknowledgeHealthReport(healthReport);\n }\n lastInitSucceeded = true;\n\n // Persist to disk\n await saveCachedConfig(result);\n\n // Handle account claim transition — write key to disk, never to stderr\n if (result.claimResult) {\n let persisted: WriteClaimedKeyResult[\"persisted\"] = \"none\";\n try {\n const w = await writeClaimedKey(result.claimResult.newApiKey);\n persisted = w.persisted;\n } catch {\n // writeClaimedKey handles its own errors internally, but guard\n // against unexpected failures to ensure claimResult is never lost\n }\n\n // When the claimed key actually reached disk, refresh the\n // managed `.glasstrace/mcp.json` (if SDK-shaped) so MCP queries\n // start using the same credential ingestion now writes traces\n // with. Refresh failure must not lose claimResult — wrap the\n // whole thing in its own try/catch.\n if (persisted !== \"none\") {\n try {\n const resolved = await resolveEffectiveMcpCredential();\n await refreshGenericMcpConfigAtRuntime(\n process.cwd(),\n resolved.effective,\n resolved.anonKey,\n );\n } catch {\n // Refresh failure leaves the existing managed config in\n // place. The next CLI-driven `glasstrace mcp add` run will\n // detect the marker mismatch and prompt a re-run.\n }\n }\n\n return { claimResult: result.claimResult };\n }\n\n return null;\n } catch (err) {\n recordInitFailure();\n failureRecorded = true;\n\n // HttpsTransportError covers DNS/TCP/TLS/timeout from the\n // node:https transport itself — `httpsPostJson` raises timeouts\n // via this error class when its internal deadlines expire.\n if (err instanceof HttpsTransportError) {\n if (/timed out|aborted/i.test(err.message)) {\n console.warn(\"[glasstrace] ingestion_unreachable: Init request timed out.\");\n } else {\n console.warn(`[glasstrace] ingestion_unreachable: ${err.message}`);\n }\n return null;\n }\n\n // Check for HTTP status errors attached by sendInitRequest\n const status = (err as Record<string, unknown>).status;\n if (status === 401) {\n console.warn(\n \"[glasstrace] ingestion_auth_failed: Check your GLASSTRACE_API_KEY.\",\n );\n return null;\n }\n\n if (status === 429) {\n console.warn(\"[glasstrace] ingestion_rate_limited: Backing off.\");\n rateLimitBackoff = true;\n return null;\n }\n\n if (typeof status === \"number\" && status >= 400) {\n console.warn(\n `[glasstrace] Init request failed with status ${status}. Using cached config.`,\n );\n return null;\n }\n\n // Schema validation failure from sendInitRequest.parse\n // NOTE: Health report was already sent to the backend (HTTP 200).\n // Not acknowledging here means the next report will double-count\n // these values. This is intentional — over-reporting is preferable\n // to data loss when the response is unparseable (DISC-1120).\n if (err instanceof Error && err.name === \"ZodError\") {\n console.warn(\n \"[glasstrace] Init response failed validation (schema version mismatch?). Using cached config.\",\n );\n return null;\n }\n\n // Network error or other fetch failure\n console.warn(\n `[glasstrace] ingestion_unreachable: ${err instanceof Error ? err.message : String(err)}`,\n );\n return null;\n }\n } catch (err) {\n // Outermost catch — safety net for unexpected throws from the inner catch\n // body itself (e.g., an error in console.warn or instanceof checks).\n // Only record the failure if the inner catch did not already do so (DISC-1121).\n if (!failureRecorded) {\n recordInitFailure();\n }\n // Guard console.warn itself: performInit MUST NOT throw. If console.warn\n // throws here (the same failure mode this catch was added to handle), swallow\n // silently rather than violating the \"never throws\" contract.\n try {\n console.warn(\n `[glasstrace] Unexpected init error: ${err instanceof Error ? err.message : String(err)}`,\n );\n } catch { /* best-effort logging; never propagate */ }\n }\n\n return null;\n}\n\n/**\n * Returns the current capture config from the three-tier fallback chain:\n * 1. In-memory config from latest init response\n * 2. File cache (read at most once per process lifetime)\n * 3. DEFAULT_CAPTURE_CONFIG\n *\n * The disk read is cached via `configCacheChecked` to avoid repeated\n * synchronous I/O on the hot path (called by GlasstraceExporter on\n * every span export batch).\n */\nexport function getActiveConfig(): CaptureConfig {\n // Tier 1: in-memory\n if (currentConfig) {\n return currentConfig.config;\n }\n\n // Tier 2: file cache (only attempt once)\n if (!configCacheChecked) {\n configCacheChecked = true;\n const cached = loadCachedConfig();\n if (cached) {\n currentConfig = cached;\n return cached.config;\n }\n }\n\n // Tier 3: defaults\n return { ...DEFAULT_CAPTURE_CONFIG };\n}\n\n/**\n * Whether side-effect / value capture is enabled by the active capture\n * config. Reads {@link getActiveConfig} on every call so config rotation\n * takes effect on the next emission. Fail-closed: any error (or absent\n * config) resolves to `false`. Internal — not exported from the package\n * barrel.\n */\nexport function isCaptureEnabled(): boolean {\n try {\n return getActiveConfig().sideEffectEvidence === true;\n } catch {\n return false;\n }\n}\n\n/**\n * Returns the `linkedAccountId` from the current in-memory init response,\n * or `undefined` if no init response is available or no account is linked.\n *\n * Used by the discovery endpoint to determine whether `claimed: true`\n * should be included in the response.\n */\nexport function getLinkedAccountId(): string | undefined {\n return currentConfig?.linkedAccountId;\n}\n\n/**\n * Returns the `claimResult` from the current in-memory init response,\n * or `undefined` if no init response is available or no claim occurred.\n *\n * Used by the discovery endpoint to detect in-flight claims: a valid\n * init response can include `claimResult` (claim happening NOW) without\n * `linkedAccountId` being set yet.\n */\nexport function getClaimResult(): SdkInitResponse[\"claimResult\"] {\n return currentConfig?.claimResult;\n}\n\n/**\n * Resets the in-memory config store. For testing only.\n */\nexport function _resetConfigForTesting(): void {\n currentConfig = null;\n configCacheChecked = false;\n rateLimitBackoff = false;\n lastInitSucceeded = false;\n transportOverride = null;\n}\n\n/**\n * Installs a test-only transport that replaces the `node:https` path\n * used by `sendInitRequest` and `performInit`. Tests use this to avoid\n * opening real sockets and to assert the SDK never routes through\n * `globalThis.fetch`. Pass `null` to restore the default transport.\n *\n * @internal Test-only. Never called from production code paths.\n */\nexport function _setTransportForTesting(fn: HttpsPostJsonFn | null): void {\n transportOverride = fn;\n}\n\n/**\n * Sets the in-memory config directly. Used by performInit and the orchestrator.\n */\nexport function _setCurrentConfig(config: SdkInitResponse): void {\n currentConfig = config;\n}\n\n/**\n * Returns whether rate-limit backoff is active. For testing only.\n */\nexport function _isRateLimitBackoff(): boolean {\n return rateLimitBackoff;\n}\n\n/**\n * Reads and clears the rate-limit backoff flag.\n * Called by the heartbeat after performInit returns null to detect 429 responses.\n * Returns true if a 429 occurred, false otherwise.\n */\nexport function consumeRateLimitFlag(): boolean {\n if (rateLimitBackoff) {\n rateLimitBackoff = false;\n return true;\n }\n return false;\n}\n\n/**\n * Returns true if the most recent performInit call completed the success path\n * (recordConfigSync + acknowledgeHealthReport were called).\n * Used by backgroundInit to decide whether to start the heartbeat.\n */\nexport function didLastInitSucceed(): boolean {\n return lastInitSucceeded;\n}\n\n/**\n * Result of {@link verifyInitReachable}.\n *\n * - `ok: true` — server acknowledged the init call with a valid, schema-\n * compliant payload. The anon key (if any) is registered server-side.\n * - `ok: false` with `reason: \"transport\"` — DNS/TCP/TLS/timeout failure.\n * No response reached the server (or couldn't be parsed off the wire).\n * `detail` is the raw cause (e.g. \"ECONNREFUSED\") with any leading\n * `fetch failed: ` prefix stripped; callers that render to the user\n * should add the prefix themselves to avoid doubling it.\n * - `ok: false` with `reason: \"rejected\"` — HTTP 4xx/5xx status. The\n * server received the call but declined it. `status` is set.\n * - `ok: false` with `reason: \"malformed\"` — HTTP 2xx but the body was\n * not valid JSON or did not match the protocol schema.\n */\nexport type VerifyInitResult =\n | { ok: true; response: SdkInitResponse }\n | { ok: false; reason: \"transport\"; detail: string }\n | { ok: false; reason: \"rejected\"; status: number; detail: string }\n | { ok: false; reason: \"malformed\"; detail: string };\n\n/**\n * Synchronously verifies that `/v1/sdk/init` is reachable and that the\n * provided anon key (if any) is registered server-side. Unlike\n * {@link performInit}, this function does NOT swallow errors — it\n * classifies them into the three user-actionable categories and\n * returns them.\n *\n * Used by the CLI `init` command to fail loudly when the init request\n * fails (DISC-493 Issue 3, DISC-494), rather than relying on the\n * runtime fire-and-forget call which can silently fail inside a\n * Next.js 16 process.\n *\n * The anon key is NEVER logged by this function. Error `detail`\n * strings are sanitized to the failure class only — the key does not\n * appear in transport, rejection, or malformed messages.\n */\nexport async function verifyInitReachable(\n config: ResolvedConfig,\n anonKey: AnonApiKey | null,\n sdkVersion: string,\n): Promise<VerifyInitResult> {\n try {\n const response = await sendInitRequest(config, anonKey, sdkVersion);\n return { ok: true, response };\n } catch (err) {\n // HTTP status error — server rejected the key.\n const status = (err as Record<string, unknown>).status;\n if (typeof status === \"number\") {\n return {\n ok: false,\n reason: \"rejected\",\n status,\n detail: `server returned HTTP ${status}`,\n };\n }\n\n // Schema validation failure (ZodError) or JSON parse error\n // (SyntaxError). Both mean the server responded but the body is\n // not a shape we can use.\n if (err instanceof Error && (err.name === \"ZodError\" || err.name === \"SyntaxError\")) {\n return {\n ok: false,\n reason: \"malformed\",\n detail: \"server returned malformed response\",\n };\n }\n\n // Everything else (transport errors, timeouts, abort, unknown) is\n // classified as transport. `detail` is the raw cause without a\n // `fetch failed:` prefix so the CLI (the only caller that renders\n // this) can format it as `fetch failed: <detail>` without risking\n // the double-prefix that would occur when the underlying error\n // already starts with `fetch failed:` (e.g., `HttpsTransportError`\n // from `sendSingleRequest`).\n const rawMessage = err instanceof Error ? err.message : String(err);\n const detail = rawMessage.startsWith(\"fetch failed: \")\n ? rawMessage.slice(\"fetch failed: \".length)\n : rawMessage;\n return { ok: false, reason: \"transport\", detail };\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
AnonApiKeySchema,
|
|
3
3
|
DevApiKeySchema,
|
|
4
4
|
createAnonApiKey
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-MKT54VEH.js";
|
|
6
6
|
import {
|
|
7
7
|
__require
|
|
8
8
|
} from "./chunk-NSBPE2FW.js";
|
|
@@ -659,4 +659,4 @@ export {
|
|
|
659
659
|
writeMcpMarker,
|
|
660
660
|
refreshGenericMcpConfigAtRuntime
|
|
661
661
|
};
|
|
662
|
-
//# sourceMappingURL=chunk-
|
|
662
|
+
//# sourceMappingURL=chunk-UOAG72NR.js.map
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-DQ25VOKK.js";
|
|
9
9
|
import {
|
|
10
10
|
GLASSTRACE_ATTRIBUTE_NAMES
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-MKT54VEH.js";
|
|
12
12
|
|
|
13
13
|
// src/async-context/index.ts
|
|
14
14
|
var ATTR = GLASSTRACE_ATTRIBUTE_NAMES;
|
|
@@ -121,4 +121,4 @@ export {
|
|
|
121
121
|
_resetForTesting,
|
|
122
122
|
withAsyncCausality
|
|
123
123
|
};
|
|
124
|
-
//# sourceMappingURL=chunk-
|
|
124
|
+
//# sourceMappingURL=chunk-YKE6HJLW.js.map
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
performInit,
|
|
55
55
|
recordSpansDropped,
|
|
56
56
|
recordSpansExported
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-SONZOTBP.js";
|
|
58
58
|
import {
|
|
59
59
|
isAnonymousMode,
|
|
60
60
|
isProductionDisabled,
|
|
@@ -65,7 +65,7 @@ import {
|
|
|
65
65
|
getOrCreateAnonKey,
|
|
66
66
|
isSyncFsAvailable,
|
|
67
67
|
readAnonKey
|
|
68
|
-
} from "./chunk-
|
|
68
|
+
} from "./chunk-UOAG72NR.js";
|
|
69
69
|
import {
|
|
70
70
|
GLASSTRACE_ATTRIBUTE_NAMES,
|
|
71
71
|
MAX_SIDE_EFFECT_SCALARS_PER_OPERATION,
|
|
@@ -79,7 +79,7 @@ import {
|
|
|
79
79
|
deriveSessionId,
|
|
80
80
|
isSideEffectScalarKey,
|
|
81
81
|
isSideEffectSemanticFieldKey
|
|
82
|
-
} from "./chunk-
|
|
82
|
+
} from "./chunk-MKT54VEH.js";
|
|
83
83
|
import {
|
|
84
84
|
isEndMarkerLine,
|
|
85
85
|
parseStartMarkerLine
|
|
@@ -4406,6 +4406,7 @@ var TOKEN_REGEX = /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/;
|
|
|
4406
4406
|
var LOCALE_REGEX = /^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8}){0,3}$/;
|
|
4407
4407
|
var TIMEZONE_REGEX = /^(?:UTC|GMT|[A-Za-z][A-Za-z0-9_+-]*(?:\/[A-Za-z0-9_+-]+){1,3})$/;
|
|
4408
4408
|
var DIGIT_REGEX = /^[0-9]+$/;
|
|
4409
|
+
var BOOL_REGEX = /^(?:true|false)$/;
|
|
4409
4410
|
var URL_SCHEME = /:\/\//;
|
|
4410
4411
|
var URL_SCHEME_RELATIVE = /^\/\//;
|
|
4411
4412
|
var EMAIL_LIKE = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i;
|
|
@@ -4440,6 +4441,9 @@ function passesFieldValidator(key, value) {
|
|
|
4440
4441
|
if (typeof key === "string" && key.endsWith("Count")) {
|
|
4441
4442
|
return DIGIT_REGEX.test(value);
|
|
4442
4443
|
}
|
|
4444
|
+
if (typeof key === "string" && key.endsWith("Holds")) {
|
|
4445
|
+
return BOOL_REGEX.test(value);
|
|
4446
|
+
}
|
|
4443
4447
|
return TOKEN_REGEX.test(value);
|
|
4444
4448
|
}
|
|
4445
4449
|
function checkOperationLabel(value) {
|
|
@@ -4546,7 +4550,7 @@ var spanState = /* @__PURE__ */ new WeakMap();
|
|
|
4546
4550
|
function getOrCreateState(span) {
|
|
4547
4551
|
let state = spanState.get(span);
|
|
4548
4552
|
if (!state) {
|
|
4549
|
-
state = { operationsRecorded: 0, omissions: /* @__PURE__ */ new Map() };
|
|
4553
|
+
state = { operationsRecorded: 0, omissions: /* @__PURE__ */ new Map(), scalarsRecorded: 0 };
|
|
4550
4554
|
spanState.set(span, state);
|
|
4551
4555
|
}
|
|
4552
4556
|
return state;
|
|
@@ -4659,6 +4663,14 @@ function attachScalar(span, key, value) {
|
|
|
4659
4663
|
} catch {
|
|
4660
4664
|
}
|
|
4661
4665
|
}
|
|
4666
|
+
function reserveScalarSlot(span) {
|
|
4667
|
+
const state = getOrCreateState(span);
|
|
4668
|
+
if (state.scalarsRecorded >= MAX_SIDE_EFFECT_SCALARS_PER_OPERATION) {
|
|
4669
|
+
return false;
|
|
4670
|
+
}
|
|
4671
|
+
state.scalarsRecorded += 1;
|
|
4672
|
+
return true;
|
|
4673
|
+
}
|
|
4662
4674
|
function recordOmission(span, reason) {
|
|
4663
4675
|
recordOmissionOnSpan(span, reason);
|
|
4664
4676
|
}
|
|
@@ -4769,6 +4781,7 @@ function runRecordSideEffect(input) {
|
|
|
4769
4781
|
recordOmission(outcome.span, "value_too_long");
|
|
4770
4782
|
return;
|
|
4771
4783
|
}
|
|
4784
|
+
const attachedFieldKeys = /* @__PURE__ */ new Set();
|
|
4772
4785
|
const fields = candidate.fields;
|
|
4773
4786
|
if (fields && typeof fields === "object") {
|
|
4774
4787
|
for (const [rawKey, rawValue] of Object.entries(fields)) {
|
|
@@ -4787,6 +4800,39 @@ function runRecordSideEffect(input) {
|
|
|
4787
4800
|
} catch {
|
|
4788
4801
|
}
|
|
4789
4802
|
attachField(outcome.span, rawKey, valueOutcome.value);
|
|
4803
|
+
attachedFieldKeys.add(rawKey);
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4806
|
+
const relations = candidate.relations;
|
|
4807
|
+
if (relations && typeof relations === "object") {
|
|
4808
|
+
for (const [rawKey, rawValue] of Object.entries(relations)) {
|
|
4809
|
+
if (attachedFieldKeys.has(rawKey)) {
|
|
4810
|
+
recordOmission(outcome.span, "unsupported_key");
|
|
4811
|
+
continue;
|
|
4812
|
+
}
|
|
4813
|
+
if (!rawKey.endsWith("Holds") || !checkSemanticFieldKey(rawKey)) {
|
|
4814
|
+
recordOmission(outcome.span, "unsupported_key");
|
|
4815
|
+
continue;
|
|
4816
|
+
}
|
|
4817
|
+
if (typeof rawValue !== "boolean") {
|
|
4818
|
+
recordOmission(outcome.span, "raw_payload");
|
|
4819
|
+
continue;
|
|
4820
|
+
}
|
|
4821
|
+
const valueOutcome = checkSemanticFieldValue(
|
|
4822
|
+
rawKey,
|
|
4823
|
+
rawValue ? "true" : "false"
|
|
4824
|
+
);
|
|
4825
|
+
if (!valueOutcome.accepted) {
|
|
4826
|
+
recordOmission(outcome.span, valueOutcome.reason);
|
|
4827
|
+
continue;
|
|
4828
|
+
}
|
|
4829
|
+
try {
|
|
4830
|
+
maybeWarnMixedCasing(rawKey, valueOutcome.value);
|
|
4831
|
+
maybeWarnPatternKeyProliferation(rawKey);
|
|
4832
|
+
} catch {
|
|
4833
|
+
}
|
|
4834
|
+
attachField(outcome.span, rawKey, valueOutcome.value);
|
|
4835
|
+
attachedFieldKeys.add(rawKey);
|
|
4790
4836
|
}
|
|
4791
4837
|
}
|
|
4792
4838
|
const scalars = candidate.scalars;
|
|
@@ -5080,11 +5126,11 @@ function registerGlasstrace(options) {
|
|
|
5080
5126
|
setCoreState(CoreState.REGISTERING);
|
|
5081
5127
|
maybeWarnStaleAgentInstructions({
|
|
5082
5128
|
projectRoot: process.cwd(),
|
|
5083
|
-
sdkVersion: "1.
|
|
5129
|
+
sdkVersion: "1.18.0"
|
|
5084
5130
|
});
|
|
5085
5131
|
startRuntimeStateWriter({
|
|
5086
5132
|
projectRoot: process.cwd(),
|
|
5087
|
-
sdkVersion: "1.
|
|
5133
|
+
sdkVersion: "1.18.0"
|
|
5088
5134
|
});
|
|
5089
5135
|
const config = resolveConfig(options);
|
|
5090
5136
|
setSideEffectVerboseFlag(config.verbose);
|
|
@@ -5252,8 +5298,8 @@ async function backgroundInit(config, anonKeyForInit, generation) {
|
|
|
5252
5298
|
if (config.verbose) {
|
|
5253
5299
|
console.info("[glasstrace] Background init firing.");
|
|
5254
5300
|
}
|
|
5255
|
-
const healthReport = collectHealthReport("1.
|
|
5256
|
-
const initResult = await performInit(config, anonKeyForInit, "1.
|
|
5301
|
+
const healthReport = collectHealthReport("1.18.0");
|
|
5302
|
+
const initResult = await performInit(config, anonKeyForInit, "1.18.0", healthReport);
|
|
5257
5303
|
if (generation !== registrationGeneration) return;
|
|
5258
5304
|
const currentState = getCoreState();
|
|
5259
5305
|
if (currentState === CoreState.SHUTTING_DOWN || currentState === CoreState.SHUTDOWN) {
|
|
@@ -5276,7 +5322,7 @@ async function backgroundInit(config, anonKeyForInit, generation) {
|
|
|
5276
5322
|
}
|
|
5277
5323
|
maybeInstallConsoleCapture();
|
|
5278
5324
|
if (didLastInitSucceed()) {
|
|
5279
|
-
startHeartbeat(config, anonKeyForInit, "1.
|
|
5325
|
+
startHeartbeat(config, anonKeyForInit, "1.18.0", generation, (newApiKey, accountId) => {
|
|
5280
5326
|
setAuthState(AuthState.CLAIMING);
|
|
5281
5327
|
emitLifecycleEvent("auth:claim_started", { accountId });
|
|
5282
5328
|
setResolvedApiKey(newApiKey);
|
|
@@ -5573,7 +5619,7 @@ async function handleSourceMapUpload(distDir) {
|
|
|
5573
5619
|
);
|
|
5574
5620
|
return;
|
|
5575
5621
|
}
|
|
5576
|
-
const { discoverSourceMapFiles, computeBuildHash, uploadSourceMaps } = await import("./source-map-uploader-
|
|
5622
|
+
const { discoverSourceMapFiles, computeBuildHash, uploadSourceMaps } = await import("./source-map-uploader-U7SLSKIZ.js");
|
|
5577
5623
|
const files = await discoverSourceMapFiles(distDir);
|
|
5578
5624
|
if (files.length === 0) {
|
|
5579
5625
|
console.info("[glasstrace] No source map files found. Skipping upload.");
|
|
@@ -5668,10 +5714,14 @@ export {
|
|
|
5668
5714
|
classifyFetchTarget,
|
|
5669
5715
|
GlasstraceExporter,
|
|
5670
5716
|
createGlasstraceSpanProcessor,
|
|
5717
|
+
checkScalarField,
|
|
5718
|
+
attachScalar,
|
|
5719
|
+
reserveScalarSlot,
|
|
5720
|
+
recordOmission,
|
|
5671
5721
|
recordSideEffect,
|
|
5672
5722
|
registerGlasstrace,
|
|
5673
5723
|
getDiscoveryHandler,
|
|
5674
5724
|
withGlasstraceConfig,
|
|
5675
5725
|
captureError
|
|
5676
5726
|
};
|
|
5677
|
-
//# sourceMappingURL=chunk-
|
|
5727
|
+
//# sourceMappingURL=chunk-Z2DSC3YI.js.map
|