@autter/runtime-node 1.6.0 → 1.6.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
@@ -184,6 +184,7 @@ Defaults (cheap by construction):
184
184
  | Request metrics | exported every 60 s |
185
185
  | Logs | not collected |
186
186
  | PII in custom attributes | redacted before export (`redactAttributes`) |
187
+ | Query strings in span URLs | parameter names only (`keepQueryParams`) |
187
188
  | Forgotten `shutdown()` | exporters still flushed on exit (`autoFlush`) |
188
189
 
189
190
  **Error-linked trace retention.** Errors export at 100% while traces are
@@ -383,6 +384,48 @@ This closes the server-side gap to match the browser relay's payload
383
384
  whitelist; it is best-effort scrubbing of obvious PII shapes, not a DLP
384
385
  engine — keep secrets out of attributes in the first place.
385
386
 
387
+ **Query strings (1.6.1+).** Query strings carry what a path never should:
388
+ emails, one-time codes, API keys, session ids, search terms. So no span
389
+ leaves the process with query values, whichever instrumentation made it
390
+ (HTTP server and client spans, undici/fetch, Express, Fastify, Next.js's own
391
+ spans):
392
+
393
+ - `http.target`, `http.url`, `url.full` and `url.original` are cut at the `?`
394
+ (and `#`): `/search?email=jane@example.com&page=2` becomes `/search`;
395
+ - `url.query` keeps only the parameter names, `email=[redacted]&page=[redacted]`.
396
+ It is added from the URL when the instrumentation did not set it, so you
397
+ still see which parameters a request used. A parameter without `=`
398
+ (`/verify?<token>`) may itself be the secret and is masked whole;
399
+ - span names lose their query too (Next.js names a request span
400
+ `GET /search?q=…` until it knows the route).
401
+
402
+ List the parameters whose values are safe to keep, such as pagination or
403
+ sorting (exact names, after percent-decoding; kept values are still scrubbed
404
+ by `redactAttributes`):
405
+
406
+ ```ts
407
+ initAutterServer({ ..., keepQueryParams: ["page", "sort"] });
408
+ // url.query: "email=[redacted]&page=2"
409
+ ```
410
+
411
+ This runs at export, like the scrubbing above, and also with
412
+ `redactAttributes: false`. Route templates (`http.route`) and request
413
+ summaries never contained a query.
414
+
415
+ ### One SDK per process, however many copies are loaded
416
+
417
+ Bundlers can load this package more than once: Next.js bundles one copy
418
+ into `instrumentation.ts` and another into each route (unless it is in
419
+ `serverExternalPackages`), and a process can load both the ESM and the CJS
420
+ build. Since 1.6.1 every copy shares one process-wide state
421
+ (`globalThis[Symbol.for("@autter/runtime-node")]`): the copy that ran
422
+ `initAutterServer` (or `initAutterLogging`) is the SDK for all of them, so
423
+ `withRuntimeRequest`, `captureException`, `runtimeLogger` and the rest export
424
+ through it from any copy, and an error one copy reported is never reported
425
+ again by another. Before any copy starts, and after `shutdown()`, every copy
426
+ behaves as before: records go to the console. The shared object is
427
+ versioned; a copy that finds a layout it does not know keeps its own state.
428
+
386
429
  ## 3. LLM tracing
387
430
 
388
431
  For the field contract, manual reporting patterns, and provider-agnostic
@@ -464,7 +464,7 @@ var _https = require('https');
464
464
  // package.json
465
465
  var package_default = {
466
466
  name: "@autter/runtime-node",
467
- version: "1.6.0",
467
+ version: "1.6.1",
468
468
  description: "Autter Runtime for Node.js: request wide events, coded errors, operation logging, same-origin browser relay and a curated OpenTelemetry server tracker",
469
469
  license: "MIT",
470
470
  type: "module",
@@ -539,6 +539,55 @@ var package_default = {
539
539
  ]
540
540
  };
541
541
 
542
+ // src/shared.ts
543
+ var SHARED_STATE_KEY = /* @__PURE__ */ Symbol.for("@autter/runtime-node");
544
+ var SHARED_STATE_LAYOUT = 1;
545
+ function isObject(value) {
546
+ return typeof value === "object" && value !== null;
547
+ }
548
+ var VERSION = package_default.version;
549
+ function resolveShared() {
550
+ const fresh = { v: SHARED_STATE_LAYOUT, sdk: VERSION };
551
+ const host = globalThis;
552
+ let existing;
553
+ try {
554
+ existing = host[SHARED_STATE_KEY];
555
+ if (existing === void 0) {
556
+ Object.defineProperty(host, SHARED_STATE_KEY, {
557
+ value: fresh,
558
+ configurable: true,
559
+ enumerable: false,
560
+ writable: false
561
+ });
562
+ return fresh;
563
+ }
564
+ } catch (e) {
565
+ return fresh;
566
+ }
567
+ if (isObject(existing) && existing.v === SHARED_STATE_LAYOUT) return existing;
568
+ debugLog(
569
+ `another copy of @autter/runtime-node uses shared state layout ${String(isObject(existing) ? existing.v : typeof existing)}; this copy (${VERSION}, layout ${SHARED_STATE_LAYOUT}) keeps its own state`
570
+ );
571
+ return fresh;
572
+ }
573
+ var shared = resolveShared();
574
+ function sharedSlot(name, init) {
575
+ const fresh = init();
576
+ try {
577
+ const existing = shared[name];
578
+ if (!isObject(existing)) {
579
+ shared[name] = fresh;
580
+ return fresh;
581
+ }
582
+ for (const [key, value] of Object.entries(fresh)) {
583
+ if (existing[key] === void 0) existing[key] = value;
584
+ }
585
+ return existing;
586
+ } catch (e2) {
587
+ return fresh;
588
+ }
589
+ }
590
+
542
591
  // src/compat.ts
543
592
  var SDK_IDENTITY = {
544
593
  name: "@autter/runtime-node",
@@ -587,7 +636,7 @@ function compatRequest(method, url, options = {}) {
587
636
  if (options.unref !== false) req.on("socket", (socket) => socket.unref());
588
637
  req.on("error", () => done(null));
589
638
  req.end(options.body);
590
- } catch (e) {
639
+ } catch (e3) {
591
640
  done(null);
592
641
  }
593
642
  });
@@ -600,7 +649,7 @@ async function fetchIngesterCompat(endpoint, options = {}) {
600
649
  let report = null;
601
650
  try {
602
651
  report = JSON.parse(res.body);
603
- } catch (e2) {
652
+ } catch (e4) {
604
653
  }
605
654
  const info = _nullishCoalesce(ingesterInfoFromReport(report), () => ( headerInfo(res.headers)));
606
655
  return info ? { info, report } : { info: null, report, error: "unrecognised /v1/compat response" };
@@ -631,7 +680,7 @@ function envDisabled() {
631
680
  return value === "0" || value === "false" || value === "off";
632
681
  }
633
682
  var defaultWarn = (message) => console.warn(`[autter-runtime] ${message}`);
634
- var state = freshState(null, false);
683
+ var compat = sharedSlot("compat", () => ({ state: freshState(null, false) }));
635
684
  function freshState(endpoint, enabled, sdk = SDK_IDENTITY, warn = defaultWarn) {
636
685
  return {
637
686
  endpoint,
@@ -646,7 +695,7 @@ function freshState(endpoint, enabled, sdk = SDK_IDENTITY, warn = defaultWarn) {
646
695
  };
647
696
  }
648
697
  function configureCompatCheck(options) {
649
- state = freshState(
698
+ compat.state = freshState(
650
699
  options.endpoint.replace(/\/$/, ""),
651
700
  options.enabled !== false && !envDisabled(),
652
701
  _nullishCoalesce(options.sdk, () => ( SDK_IDENTITY)),
@@ -654,23 +703,23 @@ function configureCompatCheck(options) {
654
703
  );
655
704
  }
656
705
  function ensureCompatConfigured(endpoint) {
657
- if (state.endpoint === null && !envDisabled()) {
658
- state = freshState(endpoint.replace(/\/$/, ""), true);
706
+ if (compat.state.endpoint === null && !envDisabled()) {
707
+ compat.state = freshState(endpoint.replace(/\/$/, ""), true);
659
708
  }
660
709
  }
661
710
  function noteCompatFeature(id) {
662
711
  try {
663
- if (!state.enabled || state.features.has(id) || !featureById(id)) return;
664
- state.features.add(id);
665
- if (state.info) {
712
+ if (!compat.state.enabled || compat.state.features.has(id) || !featureById(id)) return;
713
+ compat.state.features.add(id);
714
+ if (compat.state.info) {
666
715
  evaluateAndWarn();
667
716
  return;
668
717
  }
669
- if (state.started) return;
670
- state.started = true;
671
- const current = state;
718
+ if (compat.state.started) return;
719
+ compat.state.started = true;
720
+ const current = compat.state;
672
721
  setTimeout(() => void runCheck(current), 0).unref();
673
- } catch (e3) {
722
+ } catch (e5) {
674
723
  }
675
724
  }
676
725
  async function runCheck(current) {
@@ -679,7 +728,7 @@ async function runCheck(current) {
679
728
  const { info, error } = await fetchIngesterCompat(current.endpoint, {
680
729
  probeRoutes: routesFor(current.features)
681
730
  });
682
- if (state !== current) return;
731
+ if (compat.state !== current) return;
683
732
  if (!info) {
684
733
  debugLog(`compat check skipped: ${_nullishCoalesce(error, () => ( "ingester version unknown"))}`);
685
734
  return;
@@ -703,8 +752,8 @@ function routesFor(features) {
703
752
  return routes;
704
753
  }
705
754
  function mergeInfo(next) {
706
- const prev = state.info;
707
- state.info = {
755
+ const prev = compat.state.info;
756
+ compat.state.info = {
708
757
  version: _nullishCoalesce(_nullishCoalesce(next.version, () => ( _optionalChain([prev, 'optionalAccess', _21 => _21.version]))), () => ( null)),
709
758
  ...next.legacy || _optionalChain([prev, 'optionalAccess', _22 => _22.legacy]) ? { legacy: true } : {},
710
759
  ..._nullishCoalesce(next.schema, () => ( _optionalChain([prev, 'optionalAccess', _23 => _23.schema]))) ? { schema: _nullishCoalesce(next.schema, () => ( _optionalChain([prev, 'optionalAccess', _24 => _24.schema]))) } : {},
@@ -712,17 +761,17 @@ function mergeInfo(next) {
712
761
  };
713
762
  }
714
763
  function evaluateAndWarn() {
715
- const info = state.info;
764
+ const info = compat.state.info;
716
765
  if (!info) return;
717
- if (info.version === null && state.endpoint) {
718
- const unprobed = routesFor(state.features).filter(
719
- (route) => _optionalChain([info, 'access', _26 => _26.routes, 'optionalAccess', _27 => _27[route]]) === void 0 && !state.pendingProbes.has(route)
766
+ if (info.version === null && compat.state.endpoint) {
767
+ const unprobed = routesFor(compat.state.features).filter(
768
+ (route) => _optionalChain([info, 'access', _26 => _26.routes, 'optionalAccess', _27 => _27[route]]) === void 0 && !compat.state.pendingProbes.has(route)
720
769
  );
721
770
  if (unprobed.length) {
722
- const current = state;
771
+ const current = compat.state;
723
772
  for (const route of unprobed) current.pendingProbes.add(route);
724
773
  void Promise.all(unprobed.map((route) => probeRoute(current.endpoint, route, {}).then((exists) => [route, exists]))).then((results) => {
725
- if (state !== current) return;
774
+ if (compat.state !== current) return;
726
775
  const routes = {};
727
776
  for (const [route, exists] of results) if (exists !== null) routes[route] = exists;
728
777
  mergeInfo({ version: null, legacy: true, routes });
@@ -731,56 +780,56 @@ function evaluateAndWarn() {
731
780
  });
732
781
  }
733
782
  }
734
- for (const issue of evaluateCompat({ features: state.features, ingester: info })) {
783
+ for (const issue of evaluateCompat({ features: compat.state.features, ingester: info })) {
735
784
  warnOnce(issue);
736
785
  }
737
786
  }
738
787
  function warnOnce(issue) {
739
788
  const key = `${issue.feature}:${issue.kind}`;
740
- if (state.warned.has(key)) return;
741
- state.warned.add(key);
789
+ if (compat.state.warned.has(key)) return;
790
+ compat.state.warned.add(key);
742
791
  try {
743
- state.warn(issue.message);
744
- } catch (e4) {
792
+ compat.state.warn(issue.message);
793
+ } catch (e6) {
745
794
  }
746
795
  }
747
796
  function observeIngesterResponse(response) {
748
797
  try {
749
- if (!state.enabled) return;
798
+ if (!compat.state.enabled) return;
750
799
  const fromHeader = headerInfo(response.headers);
751
800
  if (fromHeader) {
752
- if (_optionalChain([state, 'access', _28 => _28.info, 'optionalAccess', _29 => _29.version]) !== fromHeader.version) {
801
+ if (_optionalChain([compat, 'access', _28 => _28.state, 'access', _29 => _29.info, 'optionalAccess', _30 => _30.version]) !== fromHeader.version) {
753
802
  mergeInfo(fromHeader);
754
803
  evaluateAndWarn();
755
804
  }
756
805
  return;
757
806
  }
758
- if (response.status === 404 && response.route && !_optionalChain([state, 'access', _30 => _30.info, 'optionalAccess', _31 => _31.version])) {
807
+ if (response.status === 404 && response.route && !_optionalChain([compat, 'access', _31 => _31.state, 'access', _32 => _32.info, 'optionalAccess', _33 => _33.version])) {
759
808
  mergeInfo({ version: null, legacy: true, routes: { [response.route]: false } });
760
809
  evaluateAndWarn();
761
810
  }
762
- } catch (e5) {
811
+ } catch (e7) {
763
812
  }
764
813
  }
765
814
  function noteRelayRejection(eventTypes, status, headers) {
766
815
  try {
767
- if (!state.enabled || status !== 400 || headerInfo(headers)) return [];
816
+ if (!compat.state.enabled || status !== 400 || headerInfo(headers)) return [];
768
817
  const named = [];
769
818
  for (const id of featuresForBrowserEvents(eventTypes)) {
770
819
  const feature = featureById(id);
771
820
  const key = `${id}:relay_rejected`;
772
- if (state.warned.has(key)) continue;
773
- state.warned.add(key);
821
+ if (compat.state.warned.has(key)) continue;
822
+ compat.state.warned.add(key);
774
823
  named.push(id);
775
824
  try {
776
- state.warn(
825
+ compat.state.warn(
777
826
  `The ingester rejected a browser batch containing ${feature.title.toLowerCase()} events. ${feature.title} needs ingester >= ${feature.ingester}; yours is 1.4.0 or older (it predates version reporting), so those batches are dropped. ${ingesterUpgradeHint(COMPAT_MANIFEST)}`
778
827
  );
779
- } catch (e6) {
828
+ } catch (e8) {
780
829
  }
781
830
  }
782
831
  return named;
783
- } catch (e7) {
832
+ } catch (e9) {
784
833
  return [];
785
834
  }
786
835
  }
@@ -807,4 +856,5 @@ function noteRelayRejection(eventTypes, status, headers) {
807
856
 
808
857
 
809
858
 
810
- exports.COMPAT_MANIFEST = COMPAT_MANIFEST; exports.INGESTER_VERSION_HEADER = INGESTER_VERSION_HEADER; exports.versionAtLeast = versionAtLeast; exports.featuresForBrowserEvents = featuresForBrowserEvents; exports.evaluateCompat = evaluateCompat; exports.setDebugMode = setDebugMode; exports.isDebugEnabled = isDebugEnabled; exports.debugLog = debugLog; exports.telemetryStats = telemetryStats; exports.CountingExporter = CountingExporter; exports.registerFlushTarget = registerFlushTarget; exports.unregisterFlushTargets = unregisterFlushTargets; exports.installAutterAutoFlush = installAutterAutoFlush; exports.SDK_IDENTITY = SDK_IDENTITY; exports.compatRequest = compatRequest; exports.fetchIngesterCompat = fetchIngesterCompat; exports.configureCompatCheck = configureCompatCheck; exports.ensureCompatConfigured = ensureCompatConfigured; exports.noteCompatFeature = noteCompatFeature; exports.observeIngesterResponse = observeIngesterResponse; exports.noteRelayRejection = noteRelayRejection;
859
+
860
+ exports.COMPAT_MANIFEST = COMPAT_MANIFEST; exports.INGESTER_VERSION_HEADER = INGESTER_VERSION_HEADER; exports.versionAtLeast = versionAtLeast; exports.featuresForBrowserEvents = featuresForBrowserEvents; exports.evaluateCompat = evaluateCompat; exports.setDebugMode = setDebugMode; exports.isDebugEnabled = isDebugEnabled; exports.debugLog = debugLog; exports.telemetryStats = telemetryStats; exports.CountingExporter = CountingExporter; exports.registerFlushTarget = registerFlushTarget; exports.unregisterFlushTargets = unregisterFlushTargets; exports.installAutterAutoFlush = installAutterAutoFlush; exports.sharedSlot = sharedSlot; exports.SDK_IDENTITY = SDK_IDENTITY; exports.compatRequest = compatRequest; exports.fetchIngesterCompat = fetchIngesterCompat; exports.configureCompatCheck = configureCompatCheck; exports.ensureCompatConfigured = ensureCompatConfigured; exports.noteCompatFeature = noteCompatFeature; exports.observeIngesterResponse = observeIngesterResponse; exports.noteRelayRejection = noteRelayRejection;
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  noteCompatFeature,
3
- observeIngesterResponse
4
- } from "./chunk-OY2HTUWY.js";
3
+ observeIngesterResponse,
4
+ sharedSlot
5
+ } from "./chunk-HRDELHZP.js";
5
6
 
6
7
  // ../runtime-core/dist/index.js
7
8
  var SENSITIVE_KEY_PATTERNS = [
@@ -993,7 +994,7 @@ import {
993
994
  unlinkSync
994
995
  } from "fs";
995
996
  import { join, resolve } from "path";
996
- var sinkStats = { dropped: 0 };
997
+ var sinkStats = sharedSlot("sinks", () => ({ dropped: 0 }));
997
998
  function otlpSink(options = {}) {
998
999
  let context = null;
999
1000
  let queue = [];
@@ -1362,18 +1363,22 @@ function memorySink() {
1362
1363
  }
1363
1364
 
1364
1365
  // src/logger.ts
1365
- var local = new AsyncLocalStorage();
1366
- var config = null;
1367
- var sinks = [];
1368
- var extraSinks = /* @__PURE__ */ new Set();
1366
+ var shared = sharedSlot("logger", () => ({
1367
+ local: new AsyncLocalStorage(),
1368
+ config: null,
1369
+ sinks: [],
1370
+ extraSinks: /* @__PURE__ */ new Set()
1371
+ }));
1372
+ var local = shared.local;
1373
+ var extraSinks = shared.extraSinks;
1369
1374
  var fallbackConsole = null;
1370
1375
  function redact(attributes) {
1371
- return config?.redact(attributes) ?? redactAttributes(attributes);
1376
+ return shared.config?.redact(attributes) ?? redactAttributes(attributes);
1372
1377
  }
1373
1378
  var safe = (attributes = {}) => boundContext(attributes, redact);
1374
1379
  var merge = (current, next) => mergeContext(current, next, redact);
1375
1380
  function activeSinks() {
1376
- if (config) return extraSinks.size ? [...sinks, ...extraSinks] : sinks;
1381
+ if (shared.config) return extraSinks.size ? [...shared.sinks, ...extraSinks] : shared.sinks;
1377
1382
  if (extraSinks.size) return [...extraSinks];
1378
1383
  fallbackConsole ??= consoleSink();
1379
1384
  return [fallbackConsole];
@@ -1410,9 +1415,9 @@ function emit(level, message, attributes, extra = {}, options = {}) {
1410
1415
  if (state?.sealed) return;
1411
1416
  const summary = extra["autter.event.type"] === "operation";
1412
1417
  if (!summary) recordLogBreadcrumb(level, message, attributes, state);
1413
- if (!summary && LEVEL_SEVERITY[level] < LEVEL_SEVERITY[config?.options?.minLevel ?? "debug"])
1418
+ if (!summary && LEVEL_SEVERITY[level] < LEVEL_SEVERITY[shared.config?.options?.minLevel ?? "debug"])
1414
1419
  return;
1415
- if (!summary && state && config?.options?.inline !== false) {
1420
+ if (!summary && state && shared.config?.options?.inline !== false) {
1416
1421
  const { "exception.stacktrace": _stack, ...inlineAttrs } = userContext(
1417
1422
  attributes,
1418
1423
  redact
@@ -1432,7 +1437,7 @@ function emit(level, message, attributes, extra = {}, options = {}) {
1432
1437
  ...operationAttributes(state),
1433
1438
  ...user
1434
1439
  };
1435
- const enrichers = config?.options?.enrich;
1440
+ const enrichers = shared.config?.options?.enrich;
1436
1441
  if (summary && state && enrichers?.length) {
1437
1442
  const event2 = { level, message, attributes: raw };
1438
1443
  for (const enrich of enrichers) {
@@ -1460,9 +1465,9 @@ function emit(level, message, attributes, extra = {}, options = {}) {
1460
1465
  }
1461
1466
  }
1462
1467
  function configureRuntimeLogger(next) {
1463
- config = next;
1464
- sinks = defaultSinks(next);
1465
- for (const sink of sinks) {
1468
+ shared.config = next;
1469
+ shared.sinks = defaultSinks(next);
1470
+ for (const sink of shared.sinks) {
1466
1471
  try {
1467
1472
  sink.start?.({
1468
1473
  endpoint: next.endpoint,
@@ -1477,14 +1482,14 @@ function configureRuntimeLogger(next) {
1477
1482
  }
1478
1483
  }
1479
1484
  function loggerConfigured() {
1480
- return config;
1485
+ return shared.config;
1481
1486
  }
1482
1487
  function addRuntimeSink(sink) {
1483
1488
  extraSinks.add(sink);
1484
1489
  return () => extraSinks.delete(sink);
1485
1490
  }
1486
1491
  function notifyException(error, attributes = {}) {
1487
- if (!extraSinks.size && !sinks.some((sink) => sink.exception)) return;
1492
+ if (!extraSinks.size && !shared.sinks.some((sink) => sink.exception)) return;
1488
1493
  const state = local.getStore();
1489
1494
  const record = {
1490
1495
  time: Date.now(),
@@ -1495,7 +1500,7 @@ function notifyException(error, attributes = {}) {
1495
1500
  ...state ? { operationId: state.id } : {},
1496
1501
  ...state?.requestId ? { requestId: state.requestId } : {}
1497
1502
  };
1498
- for (const sink of [...sinks, ...extraSinks]) {
1503
+ for (const sink of [...shared.sinks, ...extraSinks]) {
1499
1504
  try {
1500
1505
  sink.exception?.(record);
1501
1506
  } catch {
@@ -1504,25 +1509,25 @@ function notifyException(error, attributes = {}) {
1504
1509
  }
1505
1510
  function runtimeLogStats() {
1506
1511
  return {
1507
- buffered: sinks.reduce((sum, sink) => sum + (sink.buffered?.() ?? 0), 0),
1512
+ buffered: shared.sinks.reduce((sum, sink) => sum + (sink.buffered?.() ?? 0), 0),
1508
1513
  dropped: sinkStats.dropped
1509
1514
  };
1510
1515
  }
1511
1516
  async function flushRuntimeLogs() {
1512
- if (!config) return;
1517
+ if (!shared.config) return;
1513
1518
  const results = await Promise.allSettled(
1514
- sinks.map((sink) => Promise.resolve().then(() => sink.flush?.()))
1519
+ shared.sinks.map((sink) => Promise.resolve().then(() => sink.flush?.()))
1515
1520
  );
1516
1521
  const failed = results.find((result) => result.status === "rejected");
1517
1522
  if (failed) throw failed.reason;
1518
1523
  }
1519
1524
  async function shutdownRuntimeLogger() {
1520
- const active = sinks;
1525
+ const active = shared.sinks;
1521
1526
  const results = await Promise.allSettled(
1522
1527
  active.map((sink) => Promise.resolve().then(() => sink.shutdown?.()))
1523
1528
  );
1524
- config = null;
1525
- sinks = [];
1529
+ shared.config = null;
1530
+ shared.sinks = [];
1526
1531
  const failed = results.find((result) => result.status === "rejected");
1527
1532
  if (failed) throw failed.reason;
1528
1533
  }
@@ -1674,7 +1679,7 @@ function emitSummary(state, options = {}) {
1674
1679
  }
1675
1680
  function reportExplicitFailure(state) {
1676
1681
  if (state.outcome === "failed" && state.explicitOutcome && !state.errorThrown)
1677
- config?.reportOutcome(state.name, state.message ?? "Operation failed", {
1682
+ shared.config?.reportOutcome(state.name, state.message ?? "Operation failed", {
1678
1683
  ...traceAttributes(state),
1679
1684
  "autter.outcome.stack": state.reportingStack ?? ""
1680
1685
  });
@@ -1708,7 +1713,7 @@ function startOperation(name, fn, attributes, options, internal = {}) {
1708
1713
  thrown = error;
1709
1714
  noteOperationError(error, true, state);
1710
1715
  rememberThrownBreadcrumbs(error, state);
1711
- config?.captureThrown?.(error);
1716
+ shared.config?.captureThrown?.(error);
1712
1717
  throw error;
1713
1718
  } finally {
1714
1719
  trace.getActiveSpan()?.setAttributes({
@@ -1717,7 +1722,7 @@ function startOperation(name, fn, attributes, options, internal = {}) {
1717
1722
  "autter.operation.kind": state.kind
1718
1723
  });
1719
1724
  if (state.outcome === "failed" && !thrown)
1720
- config?.reportOutcome(
1725
+ shared.config?.reportOutcome(
1721
1726
  state.name,
1722
1727
  state.message ?? "Operation failed",
1723
1728
  {
@@ -1738,7 +1743,7 @@ function startOperation(name, fn, attributes, options, internal = {}) {
1738
1743
  }
1739
1744
  }
1740
1745
  ] : [];
1741
- const promise = config?.run ? config.run(state.name, run, traceAttributes(state), links) : run();
1746
+ const promise = shared.config?.run ? shared.config.run(state.name, run, traceAttributes(state), links) : run();
1742
1747
  if (!options.waitUntil) return promise;
1743
1748
  const waitUntil = options.waitUntil;
1744
1749
  return promise.finally(() => {
@@ -1815,9 +1820,14 @@ var runtimeContext = {
1815
1820
  // src/breadcrumbs.ts
1816
1821
  var INVALID_TRACE_ID = "00000000000000000000000000000000";
1817
1822
  var MAX_TRACE_TRAILS = 200;
1818
- var byTrace = /* @__PURE__ */ new Map();
1819
- var processTrail = new BreadcrumbTrail();
1820
- var thrownTrails = /* @__PURE__ */ new WeakMap();
1823
+ var trails = sharedSlot("breadcrumbs", () => ({
1824
+ byTrace: /* @__PURE__ */ new Map(),
1825
+ process: new BreadcrumbTrail(),
1826
+ thrown: /* @__PURE__ */ new WeakMap()
1827
+ }));
1828
+ var byTrace = trails.byTrace;
1829
+ var processTrail = trails.process;
1830
+ var thrownTrails = trails.thrown;
1821
1831
  function redact2(attributes) {
1822
1832
  return loggerConfigured()?.redact(attributes) ?? redactAttributes(attributes);
1823
1833
  }
@@ -5,7 +5,7 @@
5
5
 
6
6
 
7
7
 
8
- var _chunkARN26OK6cjs = require('./chunk-ARN26OK6.cjs');
8
+ var _chunk4MVWIRBIcjs = require('./chunk-4MVWIRBI.cjs');
9
9
 
10
10
  // src/doctor.ts
11
11
  var _fs = require('fs');
@@ -30,19 +30,19 @@ function installedSdks(cwd) {
30
30
  }
31
31
  if (_path.dirname.call(void 0, dir) === dir) break;
32
32
  }
33
- if (!found.has(_chunkARN26OK6cjs.SDK_IDENTITY.name)) found.set(_chunkARN26OK6cjs.SDK_IDENTITY.name, _chunkARN26OK6cjs.SDK_IDENTITY.version);
33
+ if (!found.has(_chunk4MVWIRBIcjs.SDK_IDENTITY.name)) found.set(_chunk4MVWIRBIcjs.SDK_IDENTITY.name, _chunk4MVWIRBIcjs.SDK_IDENTITY.version);
34
34
  return [...found].map(([name, version]) => ({ name, version }));
35
35
  }
36
36
  function featuresInUse(sdks) {
37
- return _chunkARN26OK6cjs.COMPAT_MANIFEST.features.filter(
37
+ return _chunk4MVWIRBIcjs.COMPAT_MANIFEST.features.filter(
38
38
  (feature) => sdks.some((sdk) => {
39
39
  const min = feature.sdks[sdk.name];
40
- return min !== void 0 && _chunkARN26OK6cjs.versionAtLeast.call(void 0, sdk.version, min) === true;
40
+ return min !== void 0 && _chunk4MVWIRBIcjs.versionAtLeast.call(void 0, sdk.version, min) === true;
41
41
  })
42
42
  ).map((feature) => feature.id);
43
43
  }
44
44
  async function checkKey(endpoint, key, timeoutMs) {
45
- const res = await _chunkARN26OK6cjs.compatRequest.call(void 0, "POST", `${endpoint}/v1/traces`, {
45
+ const res = await _chunk4MVWIRBIcjs.compatRequest.call(void 0, "POST", `${endpoint}/v1/traces`, {
46
46
  body: JSON.stringify({ resourceSpans: [] }),
47
47
  headers: { authorization: `Bearer ${key}` },
48
48
  unref: false,
@@ -62,10 +62,10 @@ async function runDoctor(options = {}) {
62
62
  const features = _optionalChain([options, 'access', _ => _.features, 'optionalAccess', _2 => _2.length]) ? options.features : featuresInUse(sdks);
63
63
  const routes = [
64
64
  ...new Set(
65
- features.map((id) => _optionalChain([_chunkARN26OK6cjs.COMPAT_MANIFEST, 'access', _3 => _3.features, 'access', _4 => _4.find, 'call', _5 => _5((f) => f.id === id), 'optionalAccess', _6 => _6.route])).filter((route) => Boolean(route))
65
+ features.map((id) => _optionalChain([_chunk4MVWIRBIcjs.COMPAT_MANIFEST, 'access', _3 => _3.features, 'access', _4 => _4.find, 'call', _5 => _5((f) => f.id === id), 'optionalAccess', _6 => _6.route])).filter((route) => Boolean(route))
66
66
  )
67
67
  ];
68
- const { info, error } = await _chunkARN26OK6cjs.fetchIngesterCompat.call(void 0, endpoint, { probeRoutes: routes, unref: false, timeoutMs });
68
+ const { info, error } = await _chunk4MVWIRBIcjs.fetchIngesterCompat.call(void 0, endpoint, { probeRoutes: routes, unref: false, timeoutMs });
69
69
  const issues = [];
70
70
  const seen = /* @__PURE__ */ new Set();
71
71
  const add = (issue) => {
@@ -75,15 +75,15 @@ async function runDoctor(options = {}) {
75
75
  issues.push(issue);
76
76
  }
77
77
  };
78
- _chunkARN26OK6cjs.evaluateCompat.call(void 0, { features, ingester: info }).forEach(add);
78
+ _chunk4MVWIRBIcjs.evaluateCompat.call(void 0, { features, ingester: info }).forEach(add);
79
79
  for (const sdk of sdks) {
80
80
  if (_optionalChain([options, 'access', _7 => _7.features, 'optionalAccess', _8 => _8.length])) {
81
- _chunkARN26OK6cjs.evaluateCompat.call(void 0, { features: options.features, ingester: null, sdk }).forEach(add);
81
+ _chunk4MVWIRBIcjs.evaluateCompat.call(void 0, { features: options.features, ingester: null, sdk }).forEach(add);
82
82
  }
83
83
  }
84
84
  const key = options.key ? await checkKey(endpoint, options.key, timeoutMs) : void 0;
85
85
  const featureRows = features.flatMap((id) => {
86
- const feature = _chunkARN26OK6cjs.COMPAT_MANIFEST.features.find((f) => f.id === id);
86
+ const feature = _chunk4MVWIRBIcjs.COMPAT_MANIFEST.features.find((f) => f.id === id);
87
87
  if (!feature) return [];
88
88
  const bad = issues.some((issue) => issue.feature === id);
89
89
  const known = _optionalChain([info, 'optionalAccess', _9 => _9.version]) != null || feature.route !== void 0 && _optionalChain([info, 'optionalAccess', _10 => _10.routes, 'optionalAccess', _11 => _11[feature.route]]) !== void 0;
@@ -153,7 +153,7 @@ Checks that your Autter SDKs, ingester and ClickHouse schema versions match.
153
153
  Options:
154
154
  --endpoint <url> Ingester base URL (default: $AUTTER_ENDPOINT, $OTEL_EXPORTER_OTLP_ENDPOINT, or ${DEFAULT_ENDPOINT})
155
155
  --key <key> Also verify an ingest key (default: $AUTTER_RUNTIME_KEY)
156
- --features <a,b> Only check these features (${_chunkARN26OK6cjs.COMPAT_MANIFEST.features.map((f) => f.id).join(", ")})
156
+ --features <a,b> Only check these features (${_chunk4MVWIRBIcjs.COMPAT_MANIFEST.features.map((f) => f.id).join(", ")})
157
157
  --json Machine-readable output
158
158
  --timeout <ms> Per-request timeout (default 5000)
159
159