@capxul/observability 4.1.4 → 4.2.0-rc.1

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 CHANGED
@@ -26,23 +26,28 @@ optional and snake_case; the camelCase spellings are retired.
26
26
  One boundary, two named postures, both built on the same sanitizer, token
27
27
  guard and missing-key opt-out:
28
28
 
29
- | | `utilityAppAnalytics` | `productAppAnalytics` |
30
- | --------------- | ------------------------------------------ | --------------------------------------- |
31
- | For | capability-URL apps (approval links, docs) | the Capxul product web app |
32
- | Persistence | off | **on** — J1 spans an OTP reload |
33
- | Person profiles | identified-only, nothing identifies | identified-only, identify-driven |
34
- | Autocapture | off | off |
35
- | Session replay | off | **on in every environment**, OTP masked |
36
- | Exceptions | off | on |
37
-
38
- Both stamp `capxul_env` and `producer: "browser"` through `before_send`, which
39
- also recursively removes URL query strings/fragments (including exception-frame
40
- filenames) and promoted campaign/search values. Pair either with
41
- `createPostHogBrowserPageviewTracker`, which reports pathname-only initial
42
- loads and SPA route changes. Vendor initialization and pageview capture
43
- failures degrade to disabled analytics. The product profile records ordinary
44
- text and input values. It masks inputs whose `autocomplete` attribute is
45
- `one-time-code`.
29
+ | | `utilityAppAnalytics` | `productAppAnalytics` |
30
+ | --------------- | ------------------------------------------ | ----------------------------------------------- |
31
+ | For | capability-URL apps (approval links, docs) | the Capxul product web app |
32
+ | Persistence | off | **on** — J1 spans an OTP reload |
33
+ | Person profiles | identified-only, nothing identifies | identified-only, identify-driven |
34
+ | Autocapture | off | off |
35
+ | Session replay | off | **on in every environment**, credentials masked |
36
+ | Exceptions | off | on |
37
+
38
+ Both stamp `capxul_env` and `producer: "browser"` through `before_send`.
39
+ Utility analytics removes URL queries, fragments, and promoted campaign/search
40
+ values. Product analytics preserves safe URL components and masks credentials.
41
+ Both profiles preserve the PostHog routing token required for transport.
42
+ `createPostHogBrowserPageviewTracker` reports pathname-only initial loads and
43
+ SPA route changes. Vendor initialization and pageview capture failures degrade
44
+ to disabled analytics.
45
+
46
+ [The redaction rule](../../docs/rules/redaction.md) defines the data classes.
47
+ Product replay masks password, OTP, and credential controls. Ordinary input
48
+ remains visible. Network copies mask credential fields, headers, and URL parts
49
+ while retaining safe codes, public chain evidence, and replay identifiers.
50
+ Existing event schemas and the four auth-event email hashes remain unchanged.
46
51
 
47
52
  ### Import the standard `posthog-js` build
48
53
 
@@ -0,0 +1,11 @@
1
+ {
2
+ "source": {
3
+ "commit": "efa1ae30674425e4db92020bfad513fb6cf36011",
4
+ "tree": "91f805524a7c7b53e6c28cf484b549b0c1dbd9dd",
5
+ "branch": "codex/programme-rc",
6
+ "repository": "https://github.com/Xelmar-tech/infrastructure",
7
+ "lockfileSha256": "5870f53b72706d103220c1bcf733cb4fb71196aea32a9cfbdc277d73cc959264"
8
+ },
9
+ "name": "@capxul/observability",
10
+ "version": "4.2.0-rc.1"
11
+ }
@@ -12,7 +12,7 @@ interface EngineeringTelemetryConfig {
12
12
  readonly sdkVersion: string;
13
13
  readonly serviceName?: string;
14
14
  }
15
- declare const traceHeaderFilter: (name: string) => boolean;
15
+ declare const traceHeaderFilter: (_name: string) => boolean;
16
16
  declare const postHogOtlpEndpoints: (host: string) => {
17
17
  readonly logs: `${string}/i/v1/logs`;
18
18
  readonly traces: `${string}/i/v1/traces`;
@@ -1,4 +1,5 @@
1
- import { Cause, Effect, Exit, Layer, Tracer } from "effect";
1
+ import { i as redactUrlSecrets, n as isCredentialField, r as redactSecrets } from "./src-CWfRYsBI.mjs";
2
+ import { Cause, Effect, Exit, FiberSet, Layer, Stream, Tracer } from "effect";
2
3
  import { FetchHttpClient, Headers, HttpClient } from "effect/unstable/http";
3
4
  import { OtlpExporter, OtlpLogger, OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
4
5
  //#region src/engineering.ts
@@ -8,21 +9,8 @@ const ENGINEERING_CAPXUL_ENVS = [
8
9
  "production",
9
10
  "local"
10
11
  ];
11
- const SAFE_TRACED_HEADER_NAMES = [
12
- "content-length",
13
- "content-type",
14
- "traceparent",
15
- "tracestate",
16
- "x-request-id"
17
- ];
18
- const ENGINEERING_REDACTED_HEADER_NAMES = Object.freeze([
19
- "authorization",
20
- "cookie",
21
- "set-cookie",
22
- "x-api-key",
23
- /auth|email|key|otp|secret|session|token|wallet/i
24
- ]);
25
- const traceHeaderFilter = (name) => SAFE_TRACED_HEADER_NAMES.includes(name.toLowerCase());
12
+ const traceHeaderFilter = (_name) => true;
13
+ const credentialHeaderPattern = Object.assign(/./u, { test: (name) => isCredentialField(name, "header") });
26
14
  const postHogOtlpEndpoints = (host) => {
27
15
  const base = host.replace(/\/+$/, "");
28
16
  return {
@@ -54,23 +42,79 @@ const validateEngineeringTelemetryConfig = (config) => {
54
42
  headers: Object.freeze({ authorization: headerEntries[0][1] })
55
43
  };
56
44
  };
57
- var RedactedEngineeringSpanFailure = class extends Error {
58
- constructor() {
59
- super("Engineering operation failed");
60
- this.name = "RedactedEngineeringSpanFailure";
61
- delete this.stack;
45
+ const failureText = (failure, key, limit, source) => {
46
+ if (failure === null || typeof failure !== "object") return void 0;
47
+ try {
48
+ let owner = failure;
49
+ let descriptor;
50
+ for (let depth = 0; owner !== null && depth < 4; depth += 1) {
51
+ descriptor = Object.getOwnPropertyDescriptor(owner, key);
52
+ if (descriptor !== void 0 || key !== "name") break;
53
+ owner = Object.getPrototypeOf(owner);
54
+ }
55
+ const value = descriptor?.value;
56
+ return typeof value === "string" || key === "code" && typeof value === "number" ? redactSecrets(String(value), source).slice(0, limit) : void 0;
57
+ } catch {
58
+ return;
59
+ }
60
+ };
61
+ const exportFailure = (failure, source) => {
62
+ const message = failureText(failure, "message", 2048, source) ?? (typeof failure === "string" ? redactSecrets(failure, source).slice(0, 2048) : "Engineering operation failed");
63
+ const code = failureText(failure, "code", 128);
64
+ const error = new Error((code === void 0 ? message : `${message} [code=${code}]`).slice(0, 2048));
65
+ error.name = failureText(failure, "_tag", 128) ?? failureText(failure, "name", 128) ?? "Error";
66
+ error.stack = failureText(failure, "stack", 8192, source) ?? `${error.name}: ${error.message}`;
67
+ return error;
68
+ };
69
+ const exportFailureExit = (cause, source) => Exit.failCause(Cause.fromReasons(cause.reasons.filter((reason) => !Cause.isInterruptReason(reason)).slice(0, 8).map((reason) => Cause.isFailReason(reason) ? Cause.makeFailReason(exportFailure(reason.error, source)) : Cause.makeDieReason(exportFailure(reason.defect, source)))));
70
+ const BROWSER_KEEPALIVE_BYTES = 65536;
71
+ let unfinishedBrowserExportBytes = 0;
72
+ const hasBrowserDocument = () => typeof window !== "undefined" && typeof document !== "undefined";
73
+ const browserExportClient = (client) => HttpClient.transform(client, (response, request) => Effect.acquireUseRelease(Effect.sync(() => {
74
+ const bytes = "contentLength" in request.body ? request.body.contentLength : void 0;
75
+ if (request.body._tag === "Stream" || request.body._tag === "FormData" || bytes === void 0 || !Number.isSafeInteger(bytes) || bytes < 0 || unfinishedBrowserExportBytes + bytes > BROWSER_KEEPALIVE_BYTES) return;
76
+ unfinishedBrowserExportBytes += bytes;
77
+ return bytes;
78
+ }), (bytes) => response.pipe(Effect.provideService(FetchHttpClient.RequestInit, { keepalive: bytes !== void 0 }), Effect.tap((result) => bytes === void 0 ? Effect.void : Stream.runDrain(result.stream).pipe(Effect.catch((error) => error.reason._tag === "EmptyBodyError" ? Effect.void : Effect.fail(error))))), (bytes) => Effect.sync(() => {
79
+ if (bytes !== void 0) unfinishedBrowserExportBytes -= bytes;
80
+ })));
81
+ const spanUpstream = (span) => {
82
+ let current = span;
83
+ while (current?._tag === "Span") {
84
+ const upstream = current.attributes.get("chain.upstream");
85
+ if (upstream === "alchemy" || upstream === "infura") return upstream;
86
+ current = current.parent._tag === "Some" ? current.parent.value : void 0;
62
87
  }
63
88
  };
64
- const REDACTED_SPAN_FAILURE = Exit.fail(new RedactedEngineeringSpanFailure());
65
89
  /** Preserve domain exits while preventing the OTLP serializer from seeing raw causes. */
66
90
  const makeLeakSafeEngineeringTracer = (delegate) => Tracer.make({
67
91
  span(options) {
68
92
  const span = delegate.span(options);
69
93
  const wrapped = Object.create(span);
94
+ Object.defineProperty(wrapped, "attribute", { value: (name, value) => {
95
+ const source = spanUpstream(span);
96
+ const header = /^http\.(?:request|response)\.header\.(.+)$/u.exec(name)?.[1];
97
+ if (header !== void 0 && isCredentialField(header, "header")) span.attribute(name, "[REDACTED]");
98
+ else if (typeof value === "string" && name === "url.query") span.attribute(name, redactUrlSecrets(`?${value}`).slice(1, 2049));
99
+ else if (typeof value === "string" && name === "url.path") {
100
+ const fullUrl = span.attributes.get("url.full");
101
+ const path = typeof fullUrl === "string" ? /^https?:\/\/[^/]+([^?#]*)/u.exec(fullUrl)?.[1] ?? value : value;
102
+ span.attribute(name, redactUrlSecrets(path, source).slice(0, 2048));
103
+ } else if (typeof value === "string" && (name === "url.full" || header === "location" || header === "referer" || header === "referrer")) span.attribute(name, redactUrlSecrets(value, source).slice(0, 2048));
104
+ else if (header !== void 0 && typeof value === "string") span.attribute(name, redactSecrets(value, source).slice(0, 2048));
105
+ else span.attribute(name, value);
106
+ } });
70
107
  Object.defineProperty(wrapped, "end", {
71
108
  configurable: false,
72
109
  enumerable: false,
73
- value: (endTime, exit) => span.end(endTime, Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause) ? REDACTED_SPAN_FAILURE : exit),
110
+ value: (endTime, exit) => {
111
+ if (Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause)) {
112
+ const first = exit.cause.reasons.find((reason) => !Cause.isInterruptReason(reason));
113
+ const code = failureText(first === void 0 ? void 0 : Cause.isFailReason(first) ? first.error : first.defect, "code", 128);
114
+ if (code !== void 0) span.attribute("error.code", code);
115
+ span.end(endTime, exportFailureExit(exit.cause, spanUpstream(span)));
116
+ } else span.end(endTime, exit);
117
+ },
74
118
  writable: false
75
119
  });
76
120
  return wrapped;
@@ -101,8 +145,29 @@ const makeEngineeringTelemetryLayer = (config) => {
101
145
  resource,
102
146
  mergeWithExisting: true
103
147
  });
104
- const headerPolicy = Layer.merge(Layer.succeed(HttpClient.TracerHeaderFilter, traceHeaderFilter), Layer.succeed(Headers.CurrentRedactedNames, ENGINEERING_REDACTED_HEADER_NAMES));
105
- return Layer.mergeAll(tracing, logging, headerPolicy).pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(FetchHttpClient.layer));
148
+ const headerPolicy = Layer.merge(Layer.succeed(HttpClient.TracerHeaderFilter, traceHeaderFilter), Layer.succeed(Headers.CurrentRedactedNames, [credentialHeaderPattern]));
149
+ const browserLifecycle = Layer.effectDiscard(Effect.gen(function* () {
150
+ if (validated.producer !== "browser" || !hasBrowserDocument()) return;
151
+ const browserWindow = window;
152
+ const browserDocument = document;
153
+ const flusher = yield* OtlpExporter.Flusher;
154
+ const run = yield* FiberSet.makeRuntime();
155
+ const flush = () => {
156
+ run(flusher.flush);
157
+ };
158
+ const visibilityChanged = () => {
159
+ if (browserDocument.visibilityState === "hidden") flush();
160
+ };
161
+ yield* Effect.acquireRelease(Effect.sync(() => {
162
+ browserDocument.addEventListener("visibilitychange", visibilityChanged);
163
+ browserWindow.addEventListener("pagehide", flush);
164
+ }), () => Effect.sync(() => {
165
+ browserDocument.removeEventListener("visibilitychange", visibilityChanged);
166
+ browserWindow.removeEventListener("pagehide", flush);
167
+ }));
168
+ }));
169
+ const transport = Layer.effect(HttpClient.HttpClient, Effect.map(HttpClient.HttpClient, (client) => validated.producer === "browser" && hasBrowserDocument() ? browserExportClient(client) : client)).pipe(Layer.provide(FetchHttpClient.layer));
170
+ return browserLifecycle.pipe(Layer.provideMerge(Layer.mergeAll(tracing, logging, headerPolicy)), Layer.provide(OtlpSerialization.layerJson), Layer.provide(transport));
106
171
  };
107
172
  //#endregion
108
173
  export { ENGINEERING_CAPXUL_ENVS, makeEngineeringTelemetryLayer, makeLeakSafeEngineeringTracer, postHogOtlpEndpoints, traceHeaderFilter };
package/dist/index.d.mts CHANGED
@@ -3181,12 +3181,12 @@ interface PostHogBrowserCapture {
3181
3181
  * The callback mutates and returns the supplied event, matching PostHog's
3182
3182
  * `before_send` contract while preserving the event's full structural type.
3183
3183
  */
3184
- declare function sanitizePostHogBrowserEvent<T extends PostHogBrowserCapture | null>(event: T): T;
3184
+ declare function sanitizePostHogBrowserEvent<T extends PostHogBrowserCapture | null>(event: T, urlPolicy?: "path-only" | "diagnostic"): T;
3185
3185
  /**
3186
3186
  * Drop local events before transport. Sanitize and stamp every other browser
3187
3187
  * event with `capxul_env` and `producer: "browser"`.
3188
3188
  */
3189
- declare function createCapxulBeforeSend(capxulEnv: CapxulEnv): <T extends PostHogBrowserCapture | null>(event: T) => T | null;
3189
+ declare function createCapxulBeforeSend(capxulEnv: CapxulEnv, urlPolicy?: "path-only" | "diagnostic"): <T extends PostHogBrowserCapture | null>(event: T) => T | null;
3190
3190
  type PostHogBeforeSend = ReturnType<typeof createCapxulBeforeSend>;
3191
3191
  /**
3192
3192
  * ADR-0016 posture, unchanged: capability-URL utility apps (approval links,
@@ -3227,7 +3227,7 @@ type PostHogBrowserOptions = PostHogUtilityAppOptions;
3227
3227
  * funnel lies.
3228
3228
  * - identify-driven person profiles (ADR-0020 A2 wires the calls at L1).
3229
3229
  * - session replay in EVERY environment. Ordinary text and input values remain
3230
- * visible. Inputs with `autocomplete="one-time-code"` are masked. Network
3230
+ * visible. Password, OTP, and credential controls are masked. Network
3231
3231
  * timing plus request/response headers and bodies are recorded, so a failed
3232
3232
  * call shows its status and body in the replay. Canvas stays off. The
3233
3233
  * recorder script loads from PostHog, so external dependency loading is on.
@@ -3298,17 +3298,14 @@ declare function utilityAppAnalytics(input: CapxulBrowserAnalyticsInput): PostHo
3298
3298
  * generic over the real request type and returns the same type it was given.
3299
3299
  */
3300
3300
  interface CapturedNetworkRequestLike {
3301
+ readonly name?: string | undefined;
3302
+ readonly url?: string | undefined;
3301
3303
  readonly requestHeaders?: Record<string, string> | undefined;
3302
3304
  readonly responseHeaders?: Record<string, string> | undefined;
3303
3305
  readonly requestBody?: unknown;
3304
3306
  readonly responseBody?: unknown;
3305
3307
  }
3306
- /**
3307
- * Replay keeps the request line, status, timing, headers, and bodies, so a
3308
- * failed call carries its evidence. Credential headers and secret material
3309
- * never leave the browser: bearer tokens, cookies, API keys, and the OTP a
3310
- * user just typed are masked before the recording is sent.
3311
- */
3308
+ /** Keep request evidence while masking credential values at the browser boundary. */
3312
3309
  declare function maskCapturedNetworkRequest<T extends CapturedNetworkRequestLike>(request: T): T;
3313
3310
  declare function productAppAnalytics(input: CapxulBrowserAnalyticsInput): PostHogProductAppOptions;
3314
3311
  type PostHogBrowserInitialize<Options, T> = (key: string, options: Options) => T;
package/dist/index.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { i as redactUrlSecrets, n as isCredentialField, r as redactSecrets, t as CAPXUL_ERROR_CODES } from "./src-CWfRYsBI.mjs";
1
2
  import { Effect, Schema, SchemaGetter } from "effect";
2
3
  //#region ../errors/src/chain-cause.ts
3
4
  /**
@@ -35,39 +36,6 @@ const CHAIN_UPSTREAMS = [
35
36
  "gateway"
36
37
  ];
37
38
  //#endregion
38
- //#region ../errors/src/errors.ts
39
- const CAPXUL_ERROR_CODES = [
40
- "NOT_AUTHENTICATED",
41
- "EMAIL_DELIVERY_FAILED",
42
- "PROFILE_NOT_FOUND",
43
- "SMART_ACCOUNT_MISSING",
44
- "PLAYER_NOT_FOUND",
45
- "ACCOUNT_NOT_FOUND",
46
- "ACCOUNT_CLAIMED_BY_OTHER_SIGNER",
47
- "PROVIDER_ERROR",
48
- "CAPABILITY_UNAVAILABLE",
49
- "INVALID_INPUT",
50
- "ENV_MISSING",
51
- "NOT_IMPLEMENTED",
52
- "VERIFICATION_REQUIRED",
53
- "INSUFFICIENT_BALANCE",
54
- "INVALID_RECIPIENT",
55
- "ROLE_PERMISSION_DENIED",
56
- "TRANSACTION_FAILED",
57
- "RATE_LIMITED",
58
- "NETWORK_ERROR",
59
- "UNKNOWN",
60
- "OTP_EXPIRED",
61
- "SIGNER_REJECTED",
62
- "CANCELLED",
63
- "WRONG_STATE",
64
- "STALE_EPOCH",
65
- "SUPERSEDED",
66
- "WORK_DIED",
67
- "ACTOR_STOPPED"
68
- ];
69
- new Set(CAPXUL_ERROR_CODES);
70
- //#endregion
71
39
  //#region ../types/src/index.ts
72
40
  const EVM_ADDRESS_RE = /^0x[0-9a-f]{40}$/i;
73
41
  const BYTES32_RE = /^0x[0-9a-f]{64}$/i;
@@ -1775,22 +1743,24 @@ function redactTelemetryProps(name, props, options = {}) {
1775
1743
  * The callback mutates and returns the supplied event, matching PostHog's
1776
1744
  * `before_send` contract while preserving the event's full structural type.
1777
1745
  */
1778
- function sanitizePostHogBrowserEvent(event) {
1746
+ function sanitizePostHogBrowserEvent(event, urlPolicy = "path-only") {
1779
1747
  if (event === null) return event;
1748
+ const transportToken = event.properties.token;
1780
1749
  const seen = /* @__PURE__ */ new WeakSet();
1781
- sanitizePostHogBrowserValue(event.properties, seen);
1782
- if (event.$set !== void 0) sanitizePostHogBrowserValue(event.$set, seen);
1783
- if (event.$set_once !== void 0) sanitizePostHogBrowserValue(event.$set_once, seen);
1750
+ sanitizePostHogBrowserValue(event.properties, seen, urlPolicy);
1751
+ if (transportToken !== void 0) event.properties.token = transportToken;
1752
+ if (event.$set !== void 0) sanitizePostHogBrowserValue(event.$set, seen, urlPolicy);
1753
+ if (event.$set_once !== void 0) sanitizePostHogBrowserValue(event.$set_once, seen, urlPolicy);
1784
1754
  return event;
1785
1755
  }
1786
1756
  /**
1787
1757
  * Drop local events before transport. Sanitize and stamp every other browser
1788
1758
  * event with `capxul_env` and `producer: "browser"`.
1789
1759
  */
1790
- function createCapxulBeforeSend(capxulEnv) {
1760
+ function createCapxulBeforeSend(capxulEnv, urlPolicy = "path-only") {
1791
1761
  return (event) => {
1792
1762
  if (capxulEnv === "local") return null;
1793
- const sanitized = sanitizePostHogBrowserEvent(event);
1763
+ const sanitized = sanitizePostHogBrowserEvent(event, urlPolicy);
1794
1764
  if (sanitized === null) return sanitized;
1795
1765
  sanitized.properties.capxul_env = capxulEnv;
1796
1766
  sanitized.properties.producer ??= "browser";
@@ -1821,67 +1791,64 @@ function utilityAppAnalytics(input) {
1821
1791
  before_send: createCapxulBeforeSend(input.capxulEnv)
1822
1792
  };
1823
1793
  }
1824
- const CREDENTIAL_HEADERS = /* @__PURE__ */ new Set([
1825
- "authorization",
1826
- "proxy-authorization",
1827
- "proxy-authenticate",
1828
- "www-authenticate",
1829
- "cookie",
1830
- "set-cookie",
1831
- "x-api-key",
1832
- "x-auth-token"
1833
- ]);
1834
- /** Any JSON field whose name says "credential", however it is spelled. */
1835
- const CREDENTIAL_FIELD_NAME = /(?:otp|code|pass(?:word|wd|phrase)?|token|secret|credential|api[_-]?key|private[_-]?key|authorization|cookie|session)/iu;
1836
- /** Secret material in captured text: bearer tokens, JWTs, keys, and credential fields. */
1837
- const NETWORK_TEXT_MASKS = [
1838
- [/\bBearer\s+[^\s,;"]+/giu, "Bearer [REDACTED]"],
1839
- [/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[REDACTED]"],
1840
- [/\b0x[a-fA-F0-9]{40,}\b/gu, "[REDACTED]"],
1841
- [/\b([A-Za-z_][A-Za-z0-9_.-]*=)[^&\s"]+/gu, "$1[REDACTED]"]
1842
- ];
1843
- /** `"name": "value"` JSON pairs; the name decides whether the value is masked. */
1844
- const JSON_STRING_FIELD = /("([^"\\]*)"\s*:\s*")((?:[^"\\]|\\.)*)(")/gu;
1845
- const QUERY_PAIR = /\b([A-Za-z_][A-Za-z0-9_.-]*=)[^&\s"]+/gu;
1846
- function maskNetworkText(text) {
1847
- return NETWORK_TEXT_MASKS.slice(0, 3).reduce((current, [pattern, replacement]) => current.replace(pattern, replacement), text).replace(JSON_STRING_FIELD, (whole, open, name, _value, close) => CREDENTIAL_FIELD_NAME.test(name) ? `${open}[REDACTED]${close}` : whole).replace(QUERY_PAIR, (whole, name) => CREDENTIAL_FIELD_NAME.test(name) ? `${name}[REDACTED]` : whole);
1794
+ /** In unstructured bodies, preserve text around explicitly named credentials. */
1795
+ const NETWORK_FIELD = /((?:"?)([A-Za-z_][A-Za-z0-9_.-]*)(?:"?)\s*[:=]\s*)("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|[^\s,;&}]+)/gu;
1796
+ function maskNetworkText(text, context) {
1797
+ return redactSecrets(text).replace(NETWORK_FIELD, (whole, prefix, name, value) => {
1798
+ if (!isCredentialField(name, context)) return whole;
1799
+ const quote = value.startsWith("\"") ? "\"" : value.startsWith("'") ? "'" : "";
1800
+ return `${prefix}${quote}[REDACTED]${quote}`;
1801
+ });
1848
1802
  }
1849
1803
  function maskNetworkHeaders(headers) {
1850
1804
  if (headers === void 0) return void 0;
1851
1805
  const masked = {};
1852
- for (const [name, value] of Object.entries(headers)) masked[name] = CREDENTIAL_HEADERS.has(name.toLowerCase()) ? "[REDACTED]" : maskNetworkText(value);
1806
+ for (const [name, value] of Object.entries(headers)) masked[name] = isCredentialField(name, "header") ? "[REDACTED]" : /^(?:location|content-location|referer)$/iu.test(name) ? redactUrlSecrets(value) : maskNetworkText(value);
1853
1807
  return masked;
1854
1808
  }
1855
- /** Mask by KEY at every depth: a credential-named field loses its whole value. */
1856
- function maskJsonValue(value, depth = 0) {
1857
- if (typeof value === "string") return maskNetworkText(value);
1809
+ function maskJsonValue(value, depth = 0, context) {
1810
+ if (typeof value === "function" || typeof value === "symbol") return "[UNREADABLE]";
1811
+ if (typeof value === "string") return maskNetworkText(value, context);
1858
1812
  if (typeof value !== "object" || value === null) return value;
1859
1813
  if (depth >= 16) return "[REDACTED]";
1860
- if (Array.isArray(value)) return value.map((item) => maskJsonValue(item, depth + 1));
1861
- const masked = {};
1862
- for (const [key, nested] of Object.entries(value)) masked[key] = CREDENTIAL_FIELD_NAME.test(key) ? "[REDACTED]" : maskJsonValue(nested, depth + 1);
1814
+ if (Array.isArray(value)) return value.map((item) => maskJsonValue(item, depth + 1, context));
1815
+ const masked = Object.create(null);
1816
+ for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))) {
1817
+ if (!descriptor.enumerable) continue;
1818
+ masked[key] = isCredentialField(key, context) ? "[REDACTED]" : !("value" in descriptor) ? "[UNREADABLE]" : typeof descriptor.value === "string" && isPostHogUrlProperty(key) ? redactUrlSecrets(descriptor.value) : maskJsonValue(descriptor.value, depth + 1, context);
1819
+ }
1863
1820
  return masked;
1864
1821
  }
1865
- function maskNetworkBody(body) {
1866
- if (typeof body !== "string") return body;
1822
+ function maskNetworkBody(body, context) {
1867
1823
  try {
1868
- const parsed = JSON.parse(body);
1869
- if (typeof parsed === "object" && parsed !== null) return JSON.stringify(maskJsonValue(parsed));
1870
- } catch {}
1871
- return maskNetworkText(body);
1824
+ if (typeof body !== "string") return maskJsonValue(body, 0, context);
1825
+ let parsed;
1826
+ try {
1827
+ parsed = JSON.parse(body);
1828
+ } catch {
1829
+ if (/^[^=&\s]+=[\s\S]*$/u.test(body)) return new URLSearchParams(Array.from(new URLSearchParams(body), ([name, value]) => [name, isCredentialField(name, context) ? "[REDACTED]" : maskNetworkText(value)])).toString();
1830
+ return maskNetworkText(body, context);
1831
+ }
1832
+ return JSON.stringify(maskJsonValue(parsed, 0, context));
1833
+ } catch {
1834
+ return "[UNREADABLE]";
1835
+ }
1872
1836
  }
1873
- /**
1874
- * Replay keeps the request line, status, timing, headers, and bodies, so a
1875
- * failed call carries its evidence. Credential headers and secret material
1876
- * never leave the browser: bearer tokens, cookies, API keys, and the OTP a
1877
- * user just typed are masked before the recording is sent.
1878
- */
1837
+ /** Keep request evidence while masking credential values at the browser boundary. */
1879
1838
  function maskCapturedNetworkRequest(request) {
1839
+ const requestUrl = request.name ?? request.url ?? "";
1840
+ let context;
1841
+ try {
1842
+ const path = new URL(requestUrl, "https://capxul.invalid").pathname;
1843
+ if (/\/(?:auth|oauth|sign-in|signin|login|verify-otp|verify-email)(?:\/|$)/iu.test(path)) context = "authentication";
1844
+ } catch {}
1880
1845
  return {
1881
1846
  ...request,
1847
+ ...request.name === void 0 ? {} : { name: redactUrlSecrets(request.name) },
1848
+ ...request.url === void 0 ? {} : { url: redactUrlSecrets(request.url) },
1882
1849
  requestHeaders: maskNetworkHeaders(request.requestHeaders),
1883
1850
  responseHeaders: maskNetworkHeaders(request.responseHeaders),
1884
- requestBody: maskNetworkBody(request.requestBody),
1851
+ requestBody: maskNetworkBody(request.requestBody, context),
1885
1852
  responseBody: maskNetworkBody(request.responseBody)
1886
1853
  };
1887
1854
  }
@@ -1911,13 +1878,20 @@ function productAppAnalytics(input) {
1911
1878
  save_referrer: false,
1912
1879
  session_recording: {
1913
1880
  maskAllInputs: true,
1914
- maskInputFn: (text, element) => element?.getAttribute("autocomplete")?.toLowerCase().split(/\s+/u).includes("one-time-code") ? "*".repeat(text.length) : text,
1881
+ maskInputFn: (text, element) => {
1882
+ const autocomplete = element?.getAttribute("autocomplete")?.toLowerCase().split(/\s+/u) ?? [];
1883
+ return element?.getAttribute("type")?.toLowerCase() === "password" || autocomplete.some((value) => [
1884
+ "one-time-code",
1885
+ "current-password",
1886
+ "new-password"
1887
+ ].includes(value)) || isCredentialField(element?.getAttribute("name") ?? "") || isCredentialField(element?.getAttribute("id") ?? "") ? "*".repeat(text.length) : maskNetworkText(text);
1888
+ },
1915
1889
  captureCanvas: { recordCanvas: false },
1916
1890
  recordHeaders: true,
1917
1891
  recordBody: true,
1918
1892
  maskCapturedNetworkRequestFn: maskCapturedNetworkRequest
1919
1893
  },
1920
- before_send: createCapxulBeforeSend(input.capxulEnv)
1894
+ before_send: createCapxulBeforeSend(input.capxulEnv, "diagnostic")
1921
1895
  };
1922
1896
  }
1923
1897
  /**
@@ -1974,11 +1948,15 @@ function createPostHogBrowserPageviewTracker(client) {
1974
1948
  } catch {}
1975
1949
  };
1976
1950
  }
1977
- function sanitizePostHogBrowserValue(value, seen) {
1951
+ function sanitizePostHogBrowserValue(value, seen, urlPolicy) {
1978
1952
  if (typeof value !== "object" || value === null || seen.has(value)) return;
1979
1953
  seen.add(value);
1980
1954
  if (Array.isArray(value)) {
1981
- for (const item of value) sanitizePostHogBrowserValue(item, seen);
1955
+ for (let index = 0; index < value.length; index++) {
1956
+ const item = value[index];
1957
+ if (typeof item === "string") value[index] = maskNetworkText(item);
1958
+ else sanitizePostHogBrowserValue(item, seen, urlPolicy);
1959
+ }
1982
1960
  return;
1983
1961
  }
1984
1962
  const properties = value;
@@ -1987,11 +1965,15 @@ function sanitizePostHogBrowserValue(value, seen) {
1987
1965
  delete properties[key];
1988
1966
  continue;
1989
1967
  }
1990
- if (typeof nestedValue === "string" && isPostHogUrlProperty(key)) {
1991
- properties[key] = stripUrlQueryAndFragment(nestedValue);
1968
+ if (isCredentialField(key)) {
1969
+ properties[key] = "[REDACTED]";
1970
+ continue;
1971
+ }
1972
+ if (typeof nestedValue === "string") {
1973
+ properties[key] = isPostHogUrlProperty(key) ? urlPolicy === "path-only" ? stripUrlQueryAndFragment(nestedValue) : redactUrlSecrets(nestedValue) : maskNetworkText(nestedValue);
1992
1974
  continue;
1993
1975
  }
1994
- sanitizePostHogBrowserValue(nestedValue, seen);
1976
+ sanitizePostHogBrowserValue(nestedValue, seen, urlPolicy);
1995
1977
  }
1996
1978
  }
1997
1979
  const POSTHOG_QUERY_DERIVED_PROPERTY_NAMES = [
@@ -0,0 +1,302 @@
1
+ //#region ../errors/src/secret-material.ts
2
+ const CREDENTIAL_FIELDS = /* @__PURE__ */ new Set([
3
+ "password",
4
+ "passwd",
5
+ "pwd",
6
+ "pass",
7
+ "passphrase",
8
+ "currentpassword",
9
+ "newpassword",
10
+ "confirmpassword",
11
+ "passwordconfirmation",
12
+ "passwordhash",
13
+ "hashedpassword",
14
+ "pin",
15
+ "privatekey",
16
+ "walletprivatekey",
17
+ "secretkey",
18
+ "signingkey",
19
+ "signingsecret",
20
+ "seed",
21
+ "seedphrase",
22
+ "mnemonic",
23
+ "recoveryphrase",
24
+ "recovery",
25
+ "recoverycode",
26
+ "recoverycodes",
27
+ "keyshare",
28
+ "signingmaterial",
29
+ "recoverymaterial",
30
+ "privatekeybytes",
31
+ "signingprivatekey",
32
+ "encryptionkey",
33
+ "decryptionkey",
34
+ "authtoken",
35
+ "apitoken",
36
+ "bearertoken",
37
+ "oauthtoken",
38
+ "csrftoken",
39
+ "clientassertion",
40
+ "token",
41
+ "accesstoken",
42
+ "refreshtoken",
43
+ "idtoken",
44
+ "sessiontoken",
45
+ "session",
46
+ "sessionkey",
47
+ "sessionsecret",
48
+ "authsessionid",
49
+ "sessioncredential",
50
+ "sessioncredentials",
51
+ "sessioncookie",
52
+ "auth",
53
+ "authentication",
54
+ "authorization",
55
+ "proxyauthorization",
56
+ "cookie",
57
+ "cookies",
58
+ "setcookie",
59
+ "apikey",
60
+ "xapikey",
61
+ "xauthtoken",
62
+ "clientsecret",
63
+ "secret",
64
+ "secrets",
65
+ "credential",
66
+ "credentials",
67
+ "signature",
68
+ "otp",
69
+ "otpcode",
70
+ "totpsecret",
71
+ "onetimecode",
72
+ "onetimepassword",
73
+ "verificationcode",
74
+ "authcode",
75
+ "authorizationcode",
76
+ "oauthcode",
77
+ "codeverifier"
78
+ ]);
79
+ /** Match credential fields by meaning, without hiding token addresses or replay IDs. */
80
+ function isCredentialField(name, context) {
81
+ const normalized = name.toLowerCase().replace(/[^a-z0-9]/gu, "");
82
+ return CREDENTIAL_FIELDS.has(normalized) || /(?:password(?:hash)?|passwd|passphrase|privatekey|signingkey|apikey|clientsecret|accesstoken|refreshtoken|sessiontoken)$/u.test(normalized) || context === "authentication" && (normalized === "code" || normalized === "sessionid") || context === "header" && /(?:token|secret|apikey|privatekey|signingkey|password|authorization|cookie|session)$/u.test(normalized);
83
+ }
84
+ function decodeUrlComponent(text) {
85
+ try {
86
+ return decodeURIComponent(text);
87
+ } catch {
88
+ return text;
89
+ }
90
+ }
91
+ /** Preserve URL diagnostics while removing credentials from their declared components. */
92
+ function redactUrlSecrets(value, source) {
93
+ try {
94
+ const absolute = /^[a-z][a-z0-9+.-]*:/iu.test(value);
95
+ const protocolRelative = value.startsWith("//");
96
+ const url = new URL(value, "https://redaction.invalid");
97
+ if (url.protocol !== "https:" && url.protocol !== "http:") return maskText(value);
98
+ let changed = url.username.length > 0 || url.password.length > 0;
99
+ const maskParams = (params) => new URLSearchParams(Array.from(params, ([name, entry]) => {
100
+ const safeName = maskText(name);
101
+ const safeEntry = isCredentialField(name) || /^(?:code|draft|__posthog)$/iu.test(name) ? "[REDACTED]" : maskText(entry);
102
+ changed ||= safeName !== name || safeEntry !== entry;
103
+ return [safeName, safeEntry];
104
+ })).toString();
105
+ const provider = source !== void 0 || /(?:^|\.)(?:alchemy\.com|alchemyapi\.io|infura\.io)$/iu.test(url.hostname);
106
+ const segments = (absolute || protocolRelative ? url.pathname : value.split(/[?#]/u)[0] ?? "").split("/");
107
+ const path = segments.map((segment, index) => {
108
+ const decoded = decodeUrlComponent(segment);
109
+ const masked = provider && /^v[23]$/u.test(decodeUrlComponent(segments[index - 1] ?? "")) && decoded.length > 0 ? "[REDACTED]" : maskText(decoded);
110
+ changed ||= masked !== decoded;
111
+ return masked === decoded ? segment : encodeURIComponent(masked);
112
+ }).join("/");
113
+ const query = maskParams(url.searchParams);
114
+ const fragment = url.hash.slice(1);
115
+ const decodedFragment = decodeUrlComponent(fragment);
116
+ const maskedFragment = maskText(decodedFragment);
117
+ changed ||= maskedFragment !== decodedFragment;
118
+ const fragmentQuery = fragment.indexOf("?");
119
+ const fragmentPath = fragment.slice(0, fragmentQuery);
120
+ const maskedFragmentPath = maskText(decodeUrlComponent(fragmentPath));
121
+ changed ||= maskedFragmentPath !== decodeUrlComponent(fragmentPath);
122
+ const hash = fragmentQuery >= 0 ? `${maskedFragmentPath === decodeUrlComponent(fragmentPath) ? fragmentPath : encodeURIComponent(maskedFragmentPath)}?${maskParams(new URLSearchParams(fragment.slice(fragmentQuery + 1)))}` : fragment.includes("=") ? maskParams(new URLSearchParams(fragment)) : maskedFragment === decodedFragment ? fragment : encodeURIComponent(maskedFragment);
123
+ const origin = absolute ? url.origin : protocolRelative ? `//${url.host}` : "";
124
+ if (!changed) return value;
125
+ return `${origin}${path}${query ? `?${query}` : ""}${hash ? `#${hash}` : ""}`;
126
+ } catch {
127
+ return maskText(value.replace(/(https?:\/\/)[^/\s@]+@/giu, "$1[REDACTED]@").replace(/([?&#])([^=&#]+)=([^&#]*)/gu, (whole, separator, name) => isCredentialField(decodeUrlComponent(name)) || /^(?:code|draft|__posthog)$/iu.test(decodeUrlComponent(name)) ? `${separator}${name}=[REDACTED]` : whole));
128
+ }
129
+ }
130
+ const JSON_FIELD = /("(?:[^"\\]|\\.)*")(\s*:\s*)("(?:[^"\\]|\\.)*"|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)/gu;
131
+ const PROSE_FIELD = /(?=(\b([A-Za-z][A-Za-z0-9_-]*(?:[ \t]+[A-Za-z][A-Za-z0-9_-]*){0,3})\s*[:=]\s*((?:Bearer|Basic)[ \t]+[^\s,;"']+|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|[^\s,;&"']+)))/gu;
132
+ const EMBEDDED_URL = /(https?:\/\/[^\s"<>]*)/giu;
133
+ function isSecretLabel(name) {
134
+ return isCredentialField(name) || /^request[ _-]?body$/iu.test(name);
135
+ }
136
+ const JSON_ENCODING_MAX = 8;
137
+ const JSON_NESTING_MAX = 64;
138
+ const JSON_KEY = /("(?:[^"\\]|\\.)*")\s*:/gu;
139
+ const JSON_CONTAINER_START = /(?:\{\s*["}]|\[\s*[[\]{}"0-9tfn-])/uy;
140
+ function hasSecretJsonKey(value) {
141
+ for (const match of value.matchAll(JSON_KEY)) try {
142
+ if (match[1] !== void 0 && isSecretLabel(JSON.parse(match[1]))) return true;
143
+ } catch {}
144
+ return false;
145
+ }
146
+ /** Project a complete fragment once. Never retry all of its nested substrings. */
147
+ function projectJsonFragment(value, encodingDepth, overDepth) {
148
+ if (overDepth || encodingDepth >= JSON_ENCODING_MAX) return "[REDACTED]";
149
+ let parsed;
150
+ try {
151
+ parsed = JSON.parse(value);
152
+ } catch {
153
+ return hasSecretJsonKey(value) ? "[REDACTED]" : value;
154
+ }
155
+ try {
156
+ let changed = false;
157
+ const masked = JSON.stringify(parsed, (name, nested) => {
158
+ if (isSecretLabel(name)) {
159
+ changed ||= nested !== "[REDACTED]";
160
+ return "[REDACTED]";
161
+ }
162
+ if (typeof nested === "string") {
163
+ const projected = maskJsonFragments(nested, encodingDepth + 1);
164
+ changed ||= projected !== nested;
165
+ return projected;
166
+ }
167
+ return nested;
168
+ });
169
+ return changed ? masked : value;
170
+ } catch {
171
+ return "[REDACTED]";
172
+ }
173
+ }
174
+ /** Scan balanced object/array fragments once, respecting JSON quotes and escapes. */
175
+ function maskJsonFragments(value, encodingDepth = 0) {
176
+ if (value.trimStart().startsWith("\"")) try {
177
+ const decoded = JSON.parse(value);
178
+ if (typeof decoded === "string") {
179
+ if (encodingDepth >= JSON_ENCODING_MAX) return "\"[REDACTED]\"";
180
+ const masked = maskJsonFragments(decoded, encodingDepth + 1);
181
+ return masked === decoded ? value : JSON.stringify(masked);
182
+ }
183
+ } catch {}
184
+ let output = "";
185
+ let cursor = 0;
186
+ let start = -1;
187
+ let depth = 0;
188
+ let overDepth = false;
189
+ let quoted = false;
190
+ let escaped = false;
191
+ for (let index = 0; index < value.length; index++) {
192
+ const character = value[index];
193
+ if (start < 0) {
194
+ JSON_CONTAINER_START.lastIndex = index;
195
+ if (!JSON_CONTAINER_START.test(value)) continue;
196
+ start = index;
197
+ depth = 1;
198
+ overDepth = false;
199
+ continue;
200
+ }
201
+ if (quoted) {
202
+ if (escaped) escaped = false;
203
+ else if (character === "\\") escaped = true;
204
+ else if (character === "\"") quoted = false;
205
+ continue;
206
+ }
207
+ if (character === "\"") quoted = true;
208
+ else if (character === "{" || character === "[") {
209
+ depth++;
210
+ overDepth ||= depth > JSON_NESTING_MAX;
211
+ } else if (character === "}" || character === "]") {
212
+ depth--;
213
+ if (depth !== 0) continue;
214
+ output += value.slice(cursor, start) + projectJsonFragment(value.slice(start, index + 1), encodingDepth, overDepth);
215
+ cursor = index + 1;
216
+ start = -1;
217
+ }
218
+ }
219
+ if (start >= 0) {
220
+ const fragment = value.slice(start);
221
+ output += value.slice(cursor, start) + (overDepth || hasSecretJsonKey(fragment) ? "[REDACTED]" : fragment);
222
+ cursor = value.length;
223
+ }
224
+ return output + value.slice(cursor);
225
+ }
226
+ function maskLabelledText(value) {
227
+ const jsonMasked = maskJsonFragments(value).replace(JSON_FIELD, (whole, quotedName, separator) => {
228
+ try {
229
+ return isSecretLabel(JSON.parse(quotedName)) ? `${quotedName}${separator}"[REDACTED]"` : whole;
230
+ } catch {
231
+ return whole;
232
+ }
233
+ });
234
+ let output = "";
235
+ let cursor = 0;
236
+ for (const match of jsonMasked.matchAll(PROSE_FIELD)) {
237
+ const [, whole, label, entry] = match;
238
+ if (whole === void 0 || label === void 0 || entry === void 0 || match.index < cursor || /^Bearer[ \t]+/iu.test(entry)) continue;
239
+ const words = label.split(/[ \t]+/u);
240
+ for (let index = 0; index < words.length; index++) {
241
+ const name = words.slice(index).join(" ");
242
+ if (isSecretLabel(name)) {
243
+ const prefix = words.slice(0, index).join(" ");
244
+ output += jsonMasked.slice(cursor, match.index) + `${prefix ? `${prefix} ` : ""}${name}=[REDACTED]`;
245
+ cursor = match.index + whole.length;
246
+ break;
247
+ }
248
+ }
249
+ }
250
+ return output + jsonMasked.slice(cursor);
251
+ }
252
+ const TOKEN_MASKS = [
253
+ [/\bBearer\s+(?!(?:realm|error|error_description|scope|authorization_uri|resource|claims)\s*=)[^\s,;"']+/giu, "Bearer [REDACTED]"],
254
+ [/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[REDACTED]"],
255
+ [/(^|[^a-fA-F0-9])(?<!0[xX])[a-fA-F0-9]{64}(?=$|[^a-fA-F0-9])/gu, "$1[REDACTED]"],
256
+ [/(?:(?:sk|pk|rk)_(?:live|test)|(?:phc|phx|ghp|gho|ghu|ghs|ghr|whsec)_|github_pat_|xox[baprs]-|AKIA[0-9A-Z]{16}|AIza)[A-Za-z0-9_-]*/giu, "[REDACTED]"]
257
+ ];
258
+ /** URL components and malformed URLs use this non-recursive masking layer. */
259
+ function maskText(value) {
260
+ let text = maskLabelledText(value);
261
+ for (const [pattern, replacement] of TOKEN_MASKS) text = text.replace(pattern, replacement);
262
+ return text;
263
+ }
264
+ /** Mask credential fields and embedded URLs while preserving surrounding evidence. */
265
+ function redactSecrets(value, source) {
266
+ return maskLabelledText(value).split(EMBEDDED_URL).map((part, index) => index % 2 === 1 ? redactUrlSecrets(part, source).replaceAll("%5BREDACTED%5D", "[REDACTED]") : maskText(part)).join("");
267
+ }
268
+ //#endregion
269
+ //#region ../errors/src/errors.ts
270
+ const CAPXUL_ERROR_CODES = [
271
+ "NOT_AUTHENTICATED",
272
+ "EMAIL_DELIVERY_FAILED",
273
+ "PROFILE_NOT_FOUND",
274
+ "SMART_ACCOUNT_MISSING",
275
+ "PLAYER_NOT_FOUND",
276
+ "ACCOUNT_NOT_FOUND",
277
+ "ACCOUNT_CLAIMED_BY_OTHER_SIGNER",
278
+ "PROVIDER_ERROR",
279
+ "CAPABILITY_UNAVAILABLE",
280
+ "INVALID_INPUT",
281
+ "ENV_MISSING",
282
+ "NOT_IMPLEMENTED",
283
+ "VERIFICATION_REQUIRED",
284
+ "INSUFFICIENT_BALANCE",
285
+ "INVALID_RECIPIENT",
286
+ "ROLE_PERMISSION_DENIED",
287
+ "TRANSACTION_FAILED",
288
+ "RATE_LIMITED",
289
+ "NETWORK_ERROR",
290
+ "UNKNOWN",
291
+ "OTP_EXPIRED",
292
+ "SIGNER_REJECTED",
293
+ "CANCELLED",
294
+ "WRONG_STATE",
295
+ "STALE_EPOCH",
296
+ "SUPERSEDED",
297
+ "WORK_DIED",
298
+ "ACTOR_STOPPED"
299
+ ];
300
+ new Set(CAPXUL_ERROR_CODES);
301
+ //#endregion
302
+ export { redactUrlSecrets as i, isCredentialField as n, redactSecrets as r, CAPXUL_ERROR_CODES as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capxul/observability",
3
- "version": "4.1.4",
3
+ "version": "4.2.0-rc.1",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,8 +37,8 @@
37
37
  "vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
38
38
  "vite-plus": "0.3.0",
39
39
  "vitest": "4.1.11",
40
- "@capxul/errors": "0.3.0",
41
40
  "@capxul/types": "0.3.0",
41
+ "@capxul/errors": "0.3.0",
42
42
  "@capxul/typescript-config": "0.0.0"
43
43
  },
44
44
  "scripts": {